diff --git a/docs/changelog.md b/docs/changelog.md index 159307b..62621c7 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -1,5 +1,10 @@ # Changelog +### x.y.z - in progress + +- Fixed a `KeyError` happening when `wraps` is provided with a string `new_sig`. Fixes + [#90](https://github.com/smarie/python-makefun/issues/90) + ### 1.15.1 - bugfixes - Fixed `ValueError: Invalid co_name` happening on python 2 when the name of a function to create starts or ends with diff --git a/src/makefun/main.py b/src/makefun/main.py index ffa389b..915fee3 100644 --- a/src/makefun/main.py +++ b/src/makefun/main.py @@ -213,6 +213,8 @@ def create_function(func_signature, # type: Union[str, Signature] user_provided_name = False # co_name default + # TODO this section should happen later, after and/or at the same time than + # the if isinstance(func_signature, X) user_provided_co_name = co_name is not None if not user_provided_co_name: if func_name is None: @@ -265,6 +267,9 @@ def create_function(func_signature, # type: Union[str, Signature] # fix the signature if needed elif func_name_from_str is None: func_signature_str = co_name + func_signature_str + elif user_provided_co_name: + raise ValueError("Providing both a name in the signature string and a non-none `co_name` is unsupported at" + " the moment") elif isinstance(func_signature, Signature): # create the signature string @@ -816,7 +821,7 @@ def wraps(wrapped_fun, **attrs)` In other words, as opposed to `@with_signature`, the metadata (doc, module name, etc.) is provided by the wrapped - `wrapped_fun`, so that the created function seems to be identical (except possiblyfor the signature). + `wrapped_fun`, so that the created function seems to be identical (except possibly for the signature). Note that all options in `with_signature` can still be overrided using parameters of `@wraps`. The additional `__wrapped__` attribute is set on the created function, to stay consistent @@ -955,10 +960,10 @@ def _get_args_for_wrapping(wrapped, new_sig, remove_args, prepend_args, append_a 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 + # 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__')) @@ -1012,7 +1017,7 @@ def impl(...): `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 contains a non-empty name, this name will be used instead of the one of the decorated function. Finally `None` can be provided to indicate that user wants to only change - the medatadata (func_name, doc, module_name, attrs) of the decorated function, without generating a new + the metadata (func_name, doc, module_name, attrs) of the decorated function, without generating a new function. :param inject_as_first_arg: if `True`, the created function will be injected as the first positional argument of the decorated function. This can be handy in case the implementation is shared between several facades and needs diff --git a/tests/test_issues.py b/tests/test_issues.py index abf2eae..07829f8 100644 --- a/tests/test_issues.py +++ b/tests/test_issues.py @@ -281,3 +281,19 @@ def test_issue_91(): """This test should work also in python 2 ! """ assert is_identifier("_results_bag") assert is_identifier("hello__bag") + + +def test_issue_90(): + """Test that passing a string to @wraps as new_sig works""" + + def f(a): + return 2 * a + + @wraps(f, new_sig="_results_bag()") + def g(): + return f(2) + + assert g() == 4 + assert g.__name__ == "_results_bag" + assert g.__code__.co_name == "_results_bag" + assert str(signature(g)) == "()"