Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package python-makefun for openSUSE:Factory checked in at 2022-09-30 17:57:49 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-makefun (Old) and /work/SRC/openSUSE:Factory/.python-makefun.new.2275 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-makefun" Fri Sep 30 17:57:49 2022 rev:3 rq:1007066 version:1.14.0 Changes: -------- --- /work/SRC/openSUSE:Factory/python-makefun/python-makefun.changes 2021-12-16 21:20:55.398558020 +0100 +++ /work/SRC/openSUSE:Factory/.python-makefun.new.2275/python-makefun.changes 2022-09-30 17:58:06.945289822 +0200 @@ -1,0 +2,21 @@ +Thu Sep 29 15:46:07 UTC 2022 - Yogalakshmi Arunachalam <yarunacha...@suse.com> + +- Update to Version 1.15.0 + More PEP-compliant `wraps` Latest + wraps now always sets the __wrapped__ attribute, and also sets the __signature__ attribute when the signature changes, + as specified by PEP 362. PR by #86 by lucaswiman. + +- Update to Version 1.14.0 + Support for lambda functions + create_wrapper, create_function, wraps and with_signature now support lambda functions. They also accept a new parameter + co_name to define the name to be used in the compiled code. PR #80 by andrewcleveland. + +- Update to Version 1.13.1 + - Fixed regression with generators in python 3.5 + - Fixed an issue where using partial on a generator function in python 3.5 was raising a SyntaxError. Fixed #79 + +- Update to Version 1.13.0 + - Support for async generator functions + async generator functions are now supported (See PEP525). Fixed #77. PR#78 by broglep-work. + +------------------------------------------------------------------- Old: ---- makefun-1.12.1.tar.gz New: ---- makefun-1.14.0.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-makefun.spec ++++++ --- /var/tmp/diff_new_pack.XQ6H6C/_old 2022-09-30 17:58:07.365290720 +0200 +++ /var/tmp/diff_new_pack.XQ6H6C/_new 2022-09-30 17:58:07.373290737 +0200 @@ -1,7 +1,7 @@ # # spec file for package python-makefun # -# Copyright (c) 2021 SUSE LLC +# Copyright (c) 2022 SUSE LLC # # All modifications and additions to the file contributed by third parties # remain the property of their copyright owners, unless otherwise agreed @@ -18,7 +18,7 @@ %{?!python_module:%define python_module() python-%{**} python3-%{**}} Name: python-makefun -Version: 1.12.1 +Version: 1.14.0 Release: 0 License: BSD-3-Clause Summary: Small library to dynamically create python functions ++++++ makefun-1.12.1.tar.gz -> makefun-1.14.0.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/makefun-1.12.1/LICENSE new/makefun-1.14.0/LICENSE --- old/makefun-1.12.1/LICENSE 2021-10-08 12:12:08.000000000 +0200 +++ new/makefun-1.14.0/LICENSE 2022-06-21 23:26:54.000000000 +0200 @@ -1,6 +1,6 @@ BSD 3-Clause License -Copyright (c) 2019-2020, Sylvain Mari??, Schneider Electric Industries +Copyright (c) 2019-2022, Sylvain Mari??, Schneider Electric Industries All rights reserved. Redistribution and use in source and binary forms, with or without diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/makefun-1.12.1/PKG-INFO new/makefun-1.14.0/PKG-INFO --- old/makefun-1.12.1/PKG-INFO 2021-10-08 12:13:06.000000000 +0200 +++ new/makefun-1.14.0/PKG-INFO 2022-06-21 23:27:52.000000000 +0200 @@ -1,12 +1,12 @@ Metadata-Version: 2.1 Name: makefun -Version: 1.12.1 +Version: 1.14.0 Summary: Small library to dynamically create python functions. Home-page: https://github.com/smarie/python-makefun +Download-URL: https://github.com/smarie/python-makefun/tarball/1.14.0 Author: Sylvain MARIE <sylvain.ma...@se.com> Maintainer: Sylvain MARIE <sylvain.ma...@se.com> License: BSD 3-Clause -Download-URL: https://github.com/smarie/python-makefun/tarball/1.12.1 Keywords: decorate decorator compile make dynamic function generate generation define definition signature args wrapper wraps Platform: UNKNOWN Classifier: Development Status :: 5 - Production/Stable diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/makefun-1.12.1/ci_tools/flake8-requirements.txt new/makefun-1.14.0/ci_tools/flake8-requirements.txt --- old/makefun-1.12.1/ci_tools/flake8-requirements.txt 2021-10-08 12:12:08.000000000 +0200 +++ new/makefun-1.14.0/ci_tools/flake8-requirements.txt 2022-06-21 23:26:54.000000000 +0200 @@ -2,6 +2,7 @@ flake8>=3.6,<4 flake8-html>=0.4,<1 flake8-bandit>=2.1.1,<3 +bandit<1.7.3 # temporary until this is fixed https://github.com/tylerwince/flake8-bandit/issues/21 flake8-bugbear>=20.1.0,<21.0.0 flake8-docstrings>=1.5,<2 flake8-print>=3.1.1,<4 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/makefun-1.12.1/docs/api_reference.md new/makefun-1.14.0/docs/api_reference.md --- old/makefun-1.12.1/docs/api_reference.md 2021-10-08 12:12:08.000000000 +0200 +++ new/makefun-1.14.0/docs/api_reference.md 2022-06-21 23:26:54.000000000 +0200 @@ -15,6 +15,7 @@ add_impl: bool = True, doc: str = None, qualname: str = None, + co_name: str = None, module_name: str = None, **attrs): ``` @@ -34,12 +35,18 @@ - `__annotations__` attribute is created to match the annotations in the signature. - `__doc__` attribute is copied from `func_impl.__doc__` except if overridden using `doc` - `__module__` attribute is copied from `func_impl.__module__` except if overridden using `module_name` + - `__code__.co_name` (see above) defaults to the same value as the above `__name__` attribute, except when that value is not a valid Python identifier, in which case it will be `<lambda>`. It can be overridden by providing a `co_name` that is either a valid Python identifier or `<lambda>`. Finally two new attributes are optionally created - `__source__` attribute: set if `add_source` is `True` (default), this attribute contains the source code of the generated function - `__func_impl__` attribute: set if `add_impl` is `True` (default), this attribute contains a pointer to `func_impl` +A lambda function will be created in the following cases: + +- when `func_signature` is a `Signature` object and `func_impl` is itself a lambda function, +- when the function name, either derived from a `func_signature` string, or given explicitly with `func_name`, is not a valid Python identifier, or +- when the provided `co_name` is `<lambda>`. **Parameters:** @@ -58,7 +65,9 @@ * `doc`: a string representing the docstring that will be used to set the __doc__ attribute on the generated function. If None (default), the doc of func_impl will be used. * `qualname`: a string representing the qualified name to be used. If None (default), the `__qualname__` will default to the one of `func_impl` if `func_signature` is a `Signature`, or to the name defined in `func_signature` if `func_signature` is a `str` and contains a non-empty name. - + + * `co_name`: a string representing the name to be used in the compiled code of the function. If None (default), the `__code__.co_name` will default to the one of `func_impl` if `func_signature` is a `Signature`, or to the name defined in `func_signature` if `func_signature` is a `str` and contains a non-empty name. + * `module_name`: the name of the module to be set on the function (under __module__ ). If None (default), `func_impl.__module__` will be used. * `attrs`: other keyword attributes that should be set on the function. Note that `func_impl.__dict__` is not automatically copied. @@ -73,6 +82,7 @@ add_impl: bool = True, doc: str = None, qualname: str = None, + co_name: str = None, module_name: str = None, **attrs ): @@ -104,7 +114,9 @@ * `doc`: a string representing the docstring that will be used to set the __doc__ attribute on the generated function. If None (default), the doc of the decorated function will be used. * `qualname`: a string representing the qualified name to be used. If None (default), the `__qualname__` will default to the one of `func_impl` if `func_signature` is a `Signature`, or to the name defined in `func_signature` if `func_signature` is a `str` and contains a non-empty name. - + + * `co_name`: a string representing the name to be used in the compiled code of the function. If None (default), the `__code__.co_name` will default to the one of `func_impl` if `func_signature` is a `Signature`, or to the name defined in `func_signature` if `func_signature` is a `str` and contains a non-empty name. + * `module_name`: the name of the module to be set on the function (under __module__ ). If None (default), the `__module__` attribute of the decorated function will be used. * `attrs`: other keyword attributes that should be set on the function. Note that the full `__dict__` of the decorated function is not automatically copied. @@ -124,6 +136,7 @@ add_impl: bool = True, doc: str = None, qualname: str = None, + co_name: str = None, module_name: str = None, **attrs ): @@ -180,7 +193,9 @@ - `doc`: a string representing the docstring that will be used to set the __doc__ attribute on the generated function. If None (default), the doc of `wrapped_fun` will be used. If `wrapped_fun` is an instance of `functools.partial`, a special enhanced doc will be generated. - `qualname`: a string representing the qualified name to be used. If None (default), the `__qualname__` will default to the one of `wrapped_fun`, or the one in `new_sig` if `new_sig` is provided as a string with a non-empty function name. - + + - `co_name`: a string representing the name to be used in the compiled code of the function. If None (default), the `__code__.co_name` will default to the one of `func_impl` if `func_signature` is a `Signature`, or to the name defined in `func_signature` if `func_signature` is a `str` and contains a non-empty name. + - `module_name`: the name of the module to be set on the function (under __module__ ). If None (default), the `__module__` attribute of `wrapped_fun` will be used. - `attrs`: other keyword attributes that should be set on the function. Note that the full `__dict__` of `wrapped_fun` is automatically copied. @@ -201,6 +216,7 @@ add_impl: bool = True, doc: str = None, qualname: str = None, + co_name: str = None, module_name: str = None, **attrs ): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/makefun-1.12.1/docs/changelog.md new/makefun-1.14.0/docs/changelog.md --- old/makefun-1.12.1/docs/changelog.md 2021-10-08 12:12:08.000000000 +0200 +++ new/makefun-1.14.0/docs/changelog.md 2022-06-21 23:26:54.000000000 +0200 @@ -1,5 +1,17 @@ # Changelog +### 1.14.0 - Support for lambda functions + + - `create_wrapper`, `create_function`, `wraps` and `with_signature` now support lambda functions. They also accept a new parameter `co_name` to define the name to be used in the compiled code. PR [#80](https://github.com/smarie/python-makefun/pull/80) by [andrewcleveland](https://github.com/andrewcleveland). + +### 1.13.1 - Fixed regression with generators in python 3.5 + + - Fixed an issue where using `partial` on a generator function in python 3.5 was raising a `SyntaxError`. Fixed [#79](https://github.com/smarie/python-makefun/issues/79) + +### 1.13.0 - Support for async generator functions + + - async generator functions are now supported (See [PEP525](https://www.python.org/dev/peps/pep-0525/)). Fixed [#77](https://github.com/smarie/python-makefun/issues/77). [PR#78](https://github.com/smarie/python-makefun/pull/78) by [broglep-work](https://github.com/broglep-work). + ### 1.12.1 - Bugfix - Fixed `TypeError` when a `func` attribute is present on the function provided to `create_function`. Fixed [#76](https://github.com/smarie/python-makefun/issues/76) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/makefun-1.12.1/noxfile.py new/makefun-1.14.0/noxfile.py --- old/makefun-1.12.1/noxfile.py 2021-10-08 12:12:08.000000000 +0200 +++ new/makefun-1.14.0/noxfile.py 2022-06-21 23:26:54.000000000 +0200 @@ -103,19 +103,22 @@ # Fail if the assumed python version is not the actual one session.run2("python ci_tools/check_python_version.py %s" % session.python) - # install self so that it is recognized by pytest - session.run2("pip install -e . --no-deps") - # session.install("-e", ".", "--no-deps") - # check that it can be imported even from a different folder # Important: do not surround the command into double quotes as in the shell ! - session.run('python', '-c', 'import os; os.chdir(\'./docs/\'); import %s' % pkg_name) + # session.run('python', '-c', 'import os; os.chdir(\'./docs/\'); import %s' % pkg_name) # finally run all tests if not coverage: + # install self so that it is recognized by pytest + session.run2("pip install . --no-deps") + # session.install(".", "--no-deps") + # simple: pytest only session.run2("python -m pytest --cache-clear -v tests/") else: + # install self in "develop" mode so that coverage can be measured + session.run2("pip install -e . --no-deps") + # coverage + junit html reports + badge generation session.install_reqs(phase="coverage", phase_reqs=["coverage", "pytest-html", "genbadge[tests,coverage]"], @@ -144,7 +147,7 @@ session.install("-r", str(Folders.ci_tools / "flake8-requirements.txt")) session.install("genbadge[flake8]") - session.run2("pip install -e .[flake8]") + session.run2("pip install .") rm_folder(Folders.flake8_reports) Folders.flake8_reports.mkdir(parents=True, exist_ok=True) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/makefun-1.12.1/src/makefun/_main_latest_py.py new/makefun-1.14.0/src/makefun/_main_latest_py.py --- old/makefun-1.12.1/src/makefun/_main_latest_py.py 2021-10-08 12:12:08.000000000 +0200 +++ new/makefun-1.14.0/src/makefun/_main_latest_py.py 1970-01-01 01:00:00.000000000 +0100 @@ -1,24 +0,0 @@ -# Authors: Sylvain MARIE <sylvain.ma...@se.com> -# + All contributors to <https://github.com/smarie/python-makefun> -# -# License: 3-clause BSD, <https://github.com/smarie/python-makefun/blob/master/LICENSE> -from itertools import chain - -from makefun.main import wraps - - -def make_partial_using_yield_from(new_sig, f, *preset_pos_args, **preset_kwargs): - """ - Makes a 'partial' when f is a generator and python is new enough to support `yield from` - - :param new_sig: - :param f: - :param presets: - :return: - """ - @wraps(f, new_sig) - def partial_f(*args, **kwargs): - # since the signature does the checking for us, no need to check for redundancy. - kwargs.update(preset_kwargs) # for python 3.4: explicit dict update - yield from f(*chain(preset_pos_args, args), **kwargs) - return partial_f diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/makefun-1.12.1/src/makefun/_main_py35_and_higher.py new/makefun-1.14.0/src/makefun/_main_py35_and_higher.py --- old/makefun-1.12.1/src/makefun/_main_py35_and_higher.py 1970-01-01 01:00:00.000000000 +0100 +++ new/makefun-1.14.0/src/makefun/_main_py35_and_higher.py 2022-06-21 23:26:54.000000000 +0200 @@ -0,0 +1,24 @@ +# Authors: Sylvain MARIE <sylvain.ma...@se.com> +# + All contributors to <https://github.com/smarie/python-makefun> +# +# License: 3-clause BSD, <https://github.com/smarie/python-makefun/blob/master/LICENSE> +from itertools import chain + +from makefun.main import wraps + + +def make_partial_using_yield_from(new_sig, f, *preset_pos_args, **preset_kwargs): + """ + Makes a 'partial' when f is a generator and python is new enough to support `yield from` + + :param new_sig: + :param f: + :param presets: + :return: + """ + @wraps(f, new_sig) + def partial_f(*args, **kwargs): + # since the signature does the checking for us, no need to check for redundancy. + kwargs.update(preset_kwargs) # for python 3.4: explicit dict update + yield from f(*chain(preset_pos_args, args), **kwargs) + return partial_f diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/makefun-1.12.1/src/makefun/_main_py36_and_higher.py new/makefun-1.14.0/src/makefun/_main_py36_and_higher.py --- old/makefun-1.12.1/src/makefun/_main_py36_and_higher.py 1970-01-01 01:00:00.000000000 +0100 +++ new/makefun-1.14.0/src/makefun/_main_py36_and_higher.py 2022-06-21 23:26:54.000000000 +0200 @@ -0,0 +1,26 @@ +# Authors: Sylvain MARIE <sylvain.ma...@se.com> +# + All contributors to <https://github.com/smarie/python-makefun> +# +# License: 3-clause BSD, <https://github.com/smarie/python-makefun/blob/master/LICENSE> +from itertools import chain + +from makefun.main import wraps + + +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 -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/makefun-1.12.1/src/makefun/_version.py new/makefun-1.14.0/src/makefun/_version.py --- old/makefun-1.12.1/src/makefun/_version.py 2021-10-08 12:13:06.000000000 +0200 +++ new/makefun-1.14.0/src/makefun/_version.py 2022-06-21 23:27:51.000000000 +0200 @@ -1,5 +1,5 @@ # coding: utf-8 # file generated by setuptools_scm # don't change, don't track in version control -version = '1.12.1' -version_tuple = (1, 12, 1) +__version__ = version = '1.14.0' +__version_tuple__ = version_tuple = (1, 14, 0) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/makefun-1.12.1/src/makefun/main.py new/makefun-1.14.0/src/makefun/main.py --- old/makefun-1.12.1/src/makefun/main.py 2021-10-08 12:12:08.000000000 +0200 +++ new/makefun-1.14.0/src/makefun/main.py 2022-06-21 23:26:54.000000000 +0200 @@ -11,9 +11,24 @@ from collections import OrderedDict from copy import copy from inspect import getsource +from keyword import iskeyword from textwrap import dedent from types import FunctionType + +if sys.version_info >= (3, 0): + is_identifier = str.isidentifier +else: + def is_identifier(string): + """ + Replacement for `str.isidentifier` when it is not available (e.g. on Python 2). + :param string: + :return: + """ + if len(string) == 0 or string[0].isdigit(): + return False + return all([s.isalnum() for s in string.split("_")]) + try: # python 3.3+ from inspect import signature, Signature, Parameter except ImportError: @@ -33,6 +48,13 @@ 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: @@ -66,6 +88,7 @@ add_impl=True, # type: bool doc=None, # type: str qualname=None, # type: str + co_name=None, # type: str module_name=None, # type: str **attrs ): @@ -77,7 +100,8 @@ """ return wraps(wrapped, new_sig=new_sig, prepend_args=prepend_args, append_args=append_args, remove_args=remove_args, func_name=func_name, inject_as_first_arg=inject_as_first_arg, add_source=add_source, - add_impl=add_impl, doc=doc, qualname=qualname, module_name=module_name, **attrs)(wrapper) + add_impl=add_impl, doc=doc, qualname=qualname, module_name=module_name, co_name=co_name, + **attrs)(wrapper) def getattr_partial_aware(obj, att_name, *att_default): @@ -99,6 +123,7 @@ add_impl=True, # type: bool doc=None, # type: str qualname=None, # type: str + co_name=None, # type: str module_name=None, # type: str **attrs): """ @@ -123,6 +148,9 @@ - `__annotations__` attribute is created to match the annotations in the signature. - `__doc__` attribute is copied from `func_impl.__doc__` except if overridden using `doc` - `__module__` attribute is copied from `func_impl.__module__` except if overridden using `module_name` + - `__code__.co_name` (see above) defaults to the same value as the above `__name__` attribute, except when that + value is not a valid Python identifier, in which case it will be `<lambda>`. It can be overridden by providing + a `co_name` that is either a valid Python identifier or `<lambda>`. Finally two new attributes are optionally created @@ -131,6 +159,13 @@ - `__func_impl__` attribute: set if `add_impl` is `True` (default), this attribute contains a pointer to `func_impl` + A lambda function will be created in the following cases: + + - when `func_signature` is a `Signature` object and `func_impl` is itself a lambda function, + - when the function name, either derived from a `func_signature` string, or given explicitly with `func_name`, + is not a valid Python identifier, or + - when the provided `co_name` is `<lambda>`. + :param func_signature: either a string without 'def' such as "foo(a, b: int, *args, **kwargs)" or "(a, b: int)", or a `Signature` object, for example from the output of `inspect.signature` or from the `funcsigs.signature` backport. Note that these objects can be created manually too. If the signature is provided as a string and @@ -152,6 +187,9 @@ :param qualname: a string representing the qualified name to be used. If None (default), the `__qualname__` will default to the one of `func_impl` if `func_signature` is a `Signature`, or to the name defined in `func_signature` if `func_signature` is a `str` and contains a non-empty name. + :param co_name: a string representing the name to be used in the compiled code of the function. If None (default), + the `__code__.co_name` will default to the one of `func_impl` if `func_signature` is a `Signature`, or to the + name defined in `func_signature` if `func_signature` is a `str` and contains a non-empty name. :param module_name: the name of the module to be set on the function (under __module__ ). If None (default), `func_impl.__module__` will be used. :param attrs: other keyword attributes that should be set on the function. Note that `func_impl.__dict__` is not @@ -170,10 +208,24 @@ # name defaults user_provided_name = True if func_name is None: - # allow None for now, we'll raise a ValueError later if needed + # allow None, this will result in a lambda function being created func_name = getattr_partial_aware(func_impl, '__name__', None) user_provided_name = False + # co_name default + user_provided_co_name = co_name is not None + if not user_provided_co_name: + if func_name is None: + co_name = '<lambda>' + else: + co_name = func_name + else: + if not (_is_valid_func_def_name(co_name) + or _is_lambda_func_name(co_name)): + raise ValueError("Invalid co_name %r for created function. " + "It is not possible to declare a function " + "with the provided co_name." % co_name) + # qname default user_provided_qname = True if qualname is None: @@ -201,25 +253,28 @@ func_name = func_name_from_str if not user_provided_qname: qualname = func_name + if not user_provided_co_name: + co_name = func_name + + create_lambda = not _is_valid_func_def_name(co_name) + # if lambda, strip the name, parentheses and colon from the signature + if create_lambda: + name_len = len(func_name_from_str) if func_name_from_str else 0 + func_signature_str = func_signature_str[name_len + 1: -2] # fix the signature if needed - if func_name_from_str is None: - if func_name is None: - raise ValueError("Invalid signature for created function: `None` function name. This " - "probably happened because the decorated function %s has no __name__. You may " - "wish to pass an explicit `func_name` or to complete the signature string" - "with the name before the parenthesis." % func_impl) - func_signature_str = func_name + func_signature_str + elif func_name_from_str is None: + func_signature_str = co_name + func_signature_str elif isinstance(func_signature, Signature): # create the signature string - if func_name is None: - raise ValueError("Invalid signature for created function: `None` function name. This " - "probably happened because the decorated function %s has no __name__. You may " - "wish to pass an explicit `func_name` or to provide the new signature as a " - "string containing the name" % func_impl) - func_signature_str = get_signature_string(func_name, func_signature, evaldict) + create_lambda = not _is_valid_func_def_name(co_name) + if create_lambda: + # create signature string (or argument string in the case of a lambda function + func_signature_str = get_lambda_argument_string(func_signature, evaldict) + else: + func_signature_str = get_signature_string(co_name, func_signature, evaldict) else: raise TypeError("Invalid type for `func_signature`: %s" % type(func_signature)) @@ -246,6 +301,13 @@ 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) + elif create_lambda: + if func_signature_str: + body = "lambda_ = lambda %s: _func_impl_(%s)\n" % (func_signature_str, params_str) + else: + body = "lambda_ = lambda: _func_impl_(%s)\n" % (params_str) else: body = "def %s\n return _func_impl_(%s)\n" % (func_signature_str, params_str) @@ -255,7 +317,10 @@ # create the function by compiling code, mapping the `_func_impl_` symbol to `func_impl` protect_eval_dict(evaldict, func_name, params_names) evaldict['_func_impl_'] = func_impl - f = _make(func_name, params_names, body, evaldict) + if create_lambda: + f = _make("lambda_", params_names, body, evaldict) + else: + f = _make(co_name, params_names, body, evaldict) # add the source annotation if needed if add_source: @@ -288,6 +353,24 @@ return isgeneratorfunction(func_impl) +def _is_lambda_func_name(func_name): + """ + Return True if func_name is the name of a lambda + :param func_name: + :return: + """ + return func_name == (lambda: None).__code__.co_name + + +def _is_valid_func_def_name(func_name): + """ + Return True if func_name is valid in a function definition. + :param func_name: + :return: + """ + return is_identifier(func_name) and not iskeyword(func_name) + + class _SymbolRef: """ A class used to protect signature default values and type hints when the local context would not be able @@ -357,6 +440,17 @@ return "%s%s:" % (func_name, s) +def get_lambda_argument_string(func_signature, evaldict): + """ + Returns the string to be used as arguments in a lambda function definition. + If there is a non-native symbol in the defaults, it is created as a variable in the evaldict + :param func_name: + :param func_signature: + :return: + """ + return get_signature_string('', func_signature, evaldict)[1:-2] + + TYPES_WITH_SAFE_REPR = (int, str, bytes, bool) # IMPORTANT note: float is not in the above list because not all floats have a repr that is valid for the # compiler: float('nan'), float('-inf') and float('inf') or float('+inf') have an invalid repr. @@ -685,6 +779,7 @@ append_args=None, # type: Union[str, Parameter, Iterable[Union[str, Parameter]]] remove_args=None, # type: Union[str, Iterable[str]] func_name=None, # type: str + co_name=None, # type: str inject_as_first_arg=False, # type: bool add_source=True, # type: bool add_impl=True, # type: bool @@ -765,17 +860,22 @@ :param qualname: a string representing the qualified name to be used. If None (default), the `__qualname__` will default to the one of `wrapped_fun`, or the one in `new_sig` if `new_sig` is provided as a string with a non-empty function name. + :param co_name: a string representing the name to be used in the compiled code of the function. If None (default), + the `__code__.co_name` will default to the one of `func_impl` if `func_signature` is a `Signature`, or to the + name defined in `func_signature` if `func_signature` is a `str` and contains a non-empty name. :param module_name: the name of the module to be set on the function (under __module__ ). If None (default), the `__module__` attribute of `wrapped_fun` will be used. :param attrs: other keyword attributes that should be set on the function. Note that the full `__dict__` of `wrapped_fun` is automatically copied. :return: a decorator """ - func_name, func_sig, doc, qualname, module_name, all_attrs = _get_args_for_wrapping(wrapped_fun, new_sig, - remove_args, - prepend_args, append_args, - func_name, doc, - qualname, module_name, attrs) + func_name, func_sig, doc, qualname, co_name, module_name, all_attrs = _get_args_for_wrapping(wrapped_fun, new_sig, + remove_args, + prepend_args, + append_args, + func_name, doc, + qualname, co_name, + module_name, attrs) return with_signature(func_sig, func_name=func_name, @@ -783,12 +883,13 @@ add_source=add_source, add_impl=add_impl, doc=doc, qualname=qualname, + co_name=co_name, module_name=module_name, **all_attrs) def _get_args_for_wrapping(wrapped, new_sig, remove_args, prepend_args, append_args, - func_name, doc, qualname, module_name, attrs): + func_name, doc, qualname, co_name, module_name, attrs): """ Internal method used by @wraps and create_wrapper @@ -800,6 +901,7 @@ :param func_name: :param doc: :param qualname: + :param co_name: :param module_name: :param attrs: :return: @@ -851,6 +953,10 @@ qualname = getattr_partial_aware(wrapped, '__qualname__', None) if module_name is None: module_name = getattr_partial_aware(wrapped, '__module__', None) + if co_name is None: + code = getattr_partial_aware(wrapped, '__code__', None) + if code is not None: + co_name = code.co_name # attributes: start from the wrapped dict, add '__wrapped__' if needed, and override with all attrs. all_attrs = copy(getattr_partial_aware(wrapped, '__dict__')) @@ -865,7 +971,7 @@ all_attrs['__wrapped__'] = wrapped all_attrs.update(attrs) - return func_name, func_sig, doc, qualname, module_name, all_attrs + return func_name, func_sig, doc, qualname, co_name, module_name, all_attrs def with_signature(func_signature, # type: Union[str, Signature] @@ -875,6 +981,7 @@ add_impl=True, # type: bool doc=None, # type: str qualname=None, # type: str + co_name=None, # type: str module_name=None, # type: str **attrs ): @@ -916,12 +1023,15 @@ :param qualname: a string representing the qualified name to be used. If None (default), the `__qualname__` will default to the one of `func_impl` if `func_signature` is a `Signature`, or to the name defined in `func_signature` if `func_signature` is a `str` and contains a non-empty name. + :param co_name: a string representing the name to be used in the compiled code of the function. If None (default), + the `__code__.co_name` will default to the one of `func_impl` if `func_signature` is a `Signature`, or to the + name defined in `func_signature` if `func_signature` is a `str` and contains a non-empty name. :param module_name: the name of the module to be set on the function (under __module__ ). If None (default), the `__module__` attribute of the decorated function will be used. :param attrs: other keyword attributes that should be set on the function. Note that the full `__dict__` of the decorated function is not automatically copied. """ - if func_signature is None: + if func_signature is None and co_name is None: # make sure that user does not provide non-default other args if inject_as_first_arg or not add_source or not add_impl: raise ValueError("If `func_signature=None` no new signature will be generated so only `func_name`, " @@ -950,6 +1060,7 @@ add_impl=add_impl, doc=doc, qualname=qualname, + co_name=co_name, module_name=module_name, _with_sig_=True, # special trick to tell create_function that we're @with_signature **attrs @@ -1118,11 +1229,14 @@ if _is_generator_func(f): if sys.version_info >= (3, 3): - from makefun._main_latest_py import make_partial_using_yield_from + from makefun._main_py35_and_higher import make_partial_using_yield_from partial_f = make_partial_using_yield_from(new_sig, f, *preset_pos_args, **preset_kwargs) 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_py36_and_higher 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 -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/makefun-1.12.1/src/makefun.egg-info/PKG-INFO new/makefun-1.14.0/src/makefun.egg-info/PKG-INFO --- old/makefun-1.12.1/src/makefun.egg-info/PKG-INFO 2021-10-08 12:13:06.000000000 +0200 +++ new/makefun-1.14.0/src/makefun.egg-info/PKG-INFO 2022-06-21 23:27:52.000000000 +0200 @@ -1,12 +1,12 @@ Metadata-Version: 2.1 Name: makefun -Version: 1.12.1 +Version: 1.14.0 Summary: Small library to dynamically create python functions. Home-page: https://github.com/smarie/python-makefun +Download-URL: https://github.com/smarie/python-makefun/tarball/1.14.0 Author: Sylvain MARIE <sylvain.ma...@se.com> Maintainer: Sylvain MARIE <sylvain.ma...@se.com> License: BSD 3-Clause -Download-URL: https://github.com/smarie/python-makefun/tarball/1.12.1 Keywords: decorate decorator compile make dynamic function generate generation define definition signature args wrapper wraps Platform: UNKNOWN Classifier: Development Status :: 5 - Production/Stable diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/makefun-1.12.1/src/makefun.egg-info/SOURCES.txt new/makefun-1.14.0/src/makefun.egg-info/SOURCES.txt --- old/makefun-1.12.1/src/makefun.egg-info/SOURCES.txt 2021-10-08 12:13:06.000000000 +0200 +++ new/makefun-1.14.0/src/makefun.egg-info/SOURCES.txt 2022-06-21 23:27:52.000000000 +0200 @@ -18,8 +18,9 @@ docs/index.md docs/long_description.md src/makefun/__init__.py -src/makefun/_main_latest_py.py src/makefun/_main_legacy_py.py +src/makefun/_main_py35_and_higher.py +src/makefun/_main_py36_and_higher.py src/makefun/_version.py src/makefun/main.py src/makefun/py.typed @@ -31,6 +32,7 @@ src/makefun.egg-info/top_level.txt tests/__init__.py tests/_test_py35.py +tests/_test_py36.py tests/_test_py38.py tests/test_advanced.py tests/test_compile_deco.py diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/makefun-1.12.1/tests/_test_py36.py new/makefun-1.14.0/tests/_test_py36.py --- old/makefun-1.12.1/tests/_test_py36.py 1970-01-01 01:00:00.000000000 +0100 +++ new/makefun-1.14.0/tests/_test_py36.py 2022-06-21 23:26:54.000000000 +0200 @@ -0,0 +1,17 @@ +def make_async_generator(): + """Returns a new async generator function to use in tests.""" + + async def f(v): + yield v + + return f + + +def make_async_generator_wrapper(async_gen_f): + """Returns a new async generator function wrapping `f`, to use in tests.""" + + async def wrapper(*args, **kwargs): + async for v in async_gen_f(*args, **kwargs): + yield v + + return wrapper diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/makefun-1.12.1/tests/test_advanced.py new/makefun-1.14.0/tests/test_advanced.py --- old/makefun-1.12.1/tests/test_advanced.py 2021-10-08 12:12:08.000000000 +0200 +++ new/makefun-1.14.0/tests/test_advanced.py 2022-06-21 23:26:54.000000000 +0200 @@ -5,7 +5,7 @@ from makefun.main import get_signature_from_string, with_signature -from makefun import wraps +from makefun import create_wrapper, wraps try: # python 3.3+ from inspect import signature, Signature, Parameter @@ -108,6 +108,96 @@ assert goo('hello') == 'hello' +def tests_wraps_lambda(): + """ Tests that `@wraps` can duplicate the signature of a lambda """ + foo = lambda a: a + + @wraps(foo) + def goo(*args, **kwargs): + return foo(*args, **kwargs) + + assert goo.__name__ == (lambda: None).__name__ + assert str(signature(goo)) == '(a)' + assert goo('hello') == 'hello' + + +def tests_wraps_renamed_lambda(): + """ Tests that `@wraps` can duplicate the signature of a lambda that has been renamed """ + foo = lambda a: a + foo.__name__ = 'bar' + + @wraps(foo) + def goo(*args, **kwargs): + return foo(*args, **kwargs) + + assert goo.__name__ == 'bar' + assert str(signature(goo)) == '(a)' + assert goo('hello') == 'hello' + + +def test_lambda_signature_str(): + """ Tests that `@with_signature` can create a lambda from a signature string """ + new_sig = '(a, b=5)' + + @with_signature(new_sig, func_name='<lambda>') + def foo(a, b): + return a + b + + assert foo.__name__ == '<lambda>' + assert foo.__code__.co_name == '<lambda>' + assert str(signature(foo)) == new_sig + assert foo(a=4) == 9 + + +def test_co_name(): + """ Tests that `@with_signature` can be used to change the __code__.co_name """ + @with_signature('()', co_name='bar') + def foo(): + return 'hello' + + assert foo.__name__ == 'foo' + assert foo.__code__.co_name == 'bar' + assert foo() == 'hello' + + +def test_with_signature_lambda(): + """ Tests that `@with_signature` can be used to change the __code__.co_name to `'<lambda>'` """ + @with_signature('()', co_name='<lambda>') + def foo(): + return 'hello' + + assert foo.__code__.co_name == '<lambda>' + assert foo() == 'hello' + + +def test_create_wrapper_lambda(): + """ Tests that `create_wrapper` returns a lambda function when given a lambda function to wrap""" + def foo(): + return 'hello' + bar = create_wrapper(lambda: None, foo) + + assert bar.__name__ == '<lambda>' + assert bar() == 'hello' + + +def test_invalid_co_name(): + """ Tests that `@with_signature` raises a `ValueError` when given an `co_name` that cannot be duplicated. """ + with pytest.raises(ValueError): + @with_signature('()', co_name='<invalid>') + def foo(): + return 'hello' + + +def test_invalid_func_name(): + """ Tests that `@with_signature` can duplicate a func_name that is invalid in a function definition. """ + @with_signature('()', func_name='<invalid>') + def foo(): + return 'hello' + + assert foo.__name__ == '<invalid>' + assert foo() == 'hello' + + @pytest.mark.skipif(sys.version_info < (3, 0), reason="requires python3 or higher") def test_qualname_when_nested(): """ Tests that qualname is correctly set when `@with_signature` is applied on nested functions """ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/makefun-1.12.1/tests/test_issues.py new/makefun-1.14.0/tests/test_issues.py --- old/makefun-1.12.1/tests/test_issues.py 2021-10-08 12:12:08.000000000 +0200 +++ new/makefun-1.14.0/tests/test_issues.py 2022-06-21 23:26:54.000000000 +0200 @@ -1,3 +1,4 @@ +import inspect import sys import pytest @@ -224,3 +225,31 @@ 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_77_async_generator_wraps(): + import asyncio + from ._test_py36 import make_async_generator, make_async_generator_wrapper + + f = make_async_generator() + wrapper = wraps(f)(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_77_async_generator_partial(): + import asyncio + from ._test_py36 import make_async_generator + + f = make_async_generator() + f_partial = 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 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/makefun-1.12.1/tests/test_partial_and_macros.py new/makefun-1.14.0/tests/test_partial_and_macros.py --- old/makefun-1.12.1/tests/test_partial_and_macros.py 2021-10-08 12:12:08.000000000 +0200 +++ new/makefun-1.14.0/tests/test_partial_and_macros.py 2022-06-21 23:26:54.000000000 +0200 @@ -174,10 +174,19 @@ # assert str(signature(fp_ref)) == str(signature(fp)) -def test_simple_partial_copy(): - """Test that when not providing any argument to partial, it is equivalent to wraps with new sig = None""" - def f1(a): - return a + 1 +@pytest.mark.parametrize("is_generator", [False, True]) +def test_simple_partial_copy(is_generator): + """Test that when not providing any argument to partial, it is equivalent to wraps with new sig = None + + This test was extended to cover issue 79. + """ + + if is_generator: + def f1(a): + yield a + 1 + else: + def f1(a): + return a + 1 f2 = makefun.partial(f1) @@ -188,7 +197,10 @@ f3 = makefun.wraps(f1)(f1) assert f3.__wrapped__ == f1 - assert f2(1) == f3(1) == 2 + if is_generator: + assert next(f2(1)) == next(f3(1)) == 2 + else: + assert f2(1) == f3(1) == 2 # the func attribute is there too f4 = functools.partial(f1)