From 29a40292643a948170c05dd48b2089fe078115f3 Mon Sep 17 00:00:00 2001 From: Pascal Brogle Date: Tue, 7 Dec 2021 22:19:04 +0100 Subject: [PATCH 1/3] Add support for async generator functions --- src/makefun/_main_latest_py.py | 19 +++++++++++++++++++ src/makefun/main.py | 12 ++++++++++++ tests/_test_py36.py | 21 +++++++++++++++++++++ tests/test_issues.py | 29 +++++++++++++++++++++++++++++ 4 files changed, 81 insertions(+) create mode 100644 tests/_test_py36.py diff --git a/src/makefun/_main_latest_py.py b/src/makefun/_main_latest_py.py index df89eda..ecf497e 100644 --- a/src/makefun/_main_latest_py.py +++ b/src/makefun/_main_latest_py.py @@ -22,3 +22,22 @@ def partial_f(*args, **kwargs): kwargs.update(preset_kwargs) # for python 3.4: explicit dict update yield from f(*chain(preset_pos_args, args), **kwargs) return partial_f + + +def make_partial_using_async_for_in_yield(new_sig, f, *preset_pos_args, **preset_kwargs): + """ + Makes a 'partial' when f is a async generator and python is new enough to support `async for v in f(): yield v` + + :param new_sig: + :param f: + :param presets: + :return: + """ + + @wraps(f, new_sig=new_sig) + async def partial_f(*args, **kwargs): + kwargs.update(preset_kwargs) + async for v in f(*chain(preset_pos_args, args), **kwargs): + yield v + + return partial_f diff --git a/src/makefun/main.py b/src/makefun/main.py index 7184475..5aa3073 100644 --- a/src/makefun/main.py +++ b/src/makefun/main.py @@ -33,6 +33,13 @@ def iscoroutinefunction(f): def isgeneratorfunction(f): return False +try: + from inspect import isasyncgenfunction +except ImportError: + # assume no generator function in old Python versions + def isasyncgenfunction(f): + return False + try: # python 3.5+ from typing import Callable, Any, Union, Iterable, Dict, Tuple, Mapping except ImportError: @@ -246,6 +253,8 @@ def create_function(func_signature, # type: Union[str, Signature] else: from makefun._main_legacy_py import get_legacy_py_generator_body_template body = get_legacy_py_generator_body_template() % (func_signature_str, params_str) + elif isasyncgenfunction(func_impl): + body = "async def %s\n async for y in _func_impl_(%s):\n yield y\n" % (func_signature_str, params_str) else: body = "def %s\n return _func_impl_(%s)\n" % (func_signature_str, params_str) @@ -1123,6 +1132,9 @@ def partial(f, # type: Callable else: from makefun._main_legacy_py import make_partial_using_yield partial_f = make_partial_using_yield(new_sig, f, *preset_pos_args, **preset_kwargs) + elif isasyncgenfunction(f) and sys.version_info >= (3, 6): + from makefun._main_latest_py import make_partial_using_async_for_in_yield + partial_f = make_partial_using_async_for_in_yield(new_sig, f, *preset_pos_args, **preset_kwargs) else: @wraps(f, new_sig=new_sig) def partial_f(*args, **kwargs): diff --git a/tests/_test_py36.py b/tests/_test_py36.py new file mode 100644 index 0000000..d6d0358 --- /dev/null +++ b/tests/_test_py36.py @@ -0,0 +1,21 @@ +from makefun import wraps, partial + + +def make_async_generator(): + async def f(v): + yield v + + return f + + +def make_async_generator_wrapper(f): + @wraps(f) + async def wrapper(*args, **kwargs): + async for v in f(*args, **kwargs): + yield v + + return wrapper + + +def make_async_generator_partial(f, *args, **kwargs): + return partial(f, *args, **kwargs) diff --git a/tests/test_issues.py b/tests/test_issues.py index c12334c..cf6b80b 100644 --- a/tests/test_issues.py +++ b/tests/test_issues.py @@ -1,3 +1,4 @@ +import inspect import sys import pytest @@ -224,3 +225,31 @@ def f(a): f2 = create_function("zoo(a)", f, func=f) assert f2(3) == 4 + + +@pytest.mark.skipif(sys.version_info < (3, 6), reason="requires python 3.6 or higher (async generator)") +def test_issue_async_generator_wraps(): + import asyncio + from ._test_py36 import make_async_generator, make_async_generator_wrapper + + f = make_async_generator() + wrapper = make_async_generator_wrapper(f) + + assert inspect.isasyncgenfunction(f) + assert inspect.isasyncgenfunction(wrapper) + + assert asyncio.get_event_loop().run_until_complete(asyncio.ensure_future(wrapper(1).__anext__())) == 1 + + +@pytest.mark.skipif(sys.version_info < (3, 6), reason="requires python 3.6 or higher (async generator)") +def test_issue_async_generator_partial(): + import asyncio + from ._test_py36 import make_async_generator, make_async_generator_partial + + f = make_async_generator() + f_partial = make_async_generator_partial(f, v=1) + + assert inspect.isasyncgenfunction(f) + assert inspect.isasyncgenfunction(f_partial) + + assert asyncio.get_event_loop().run_until_complete(asyncio.ensure_future(f_partial().__anext__())) == 1 From d0f66cbf5d5c25a51d9cfbea46381e3442f02d50 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sylvain=20Mari=C3=A9?= Date: Tue, 4 Jan 2022 21:06:13 +0100 Subject: [PATCH 2/3] Update tests/test_issues.py --- tests/test_issues.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_issues.py b/tests/test_issues.py index cf6b80b..fb844fd 100644 --- a/tests/test_issues.py +++ b/tests/test_issues.py @@ -242,7 +242,7 @@ def test_issue_async_generator_wraps(): @pytest.mark.skipif(sys.version_info < (3, 6), reason="requires python 3.6 or higher (async generator)") -def test_issue_async_generator_partial(): +def test_issue_77_async_generator_partial(): import asyncio from ._test_py36 import make_async_generator, make_async_generator_partial From 0b82141cf3f15d67cff5e104888e108bc04666be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sylvain=20Mari=C3=A9?= Date: Tue, 4 Jan 2022 21:06:19 +0100 Subject: [PATCH 3/3] Update tests/test_issues.py --- tests/test_issues.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_issues.py b/tests/test_issues.py index fb844fd..d69885f 100644 --- a/tests/test_issues.py +++ b/tests/test_issues.py @@ -228,7 +228,7 @@ def f(a): @pytest.mark.skipif(sys.version_info < (3, 6), reason="requires python 3.6 or higher (async generator)") -def test_issue_async_generator_wraps(): +def test_issue_77_async_generator_wraps(): import asyncio from ._test_py36 import make_async_generator, make_async_generator_wrapper