opened
aulemahal
username_0
Hi!

With boltons 20.0.0 (on Linux), I get errors when trying to wrap partials. My goal is to fix the docstring and signature of the wrapped partial, so it acts as a normal function, with an option to "hide" the given arguments with "injected".

Examples:
```
from functools import partial
from boltons.funcutils import wraps

def func(a, b=1, c=1):
    """Docstring"""
    return (a, b, c)

newf = wraps(func)(partial(func, b=2))
newf(1)
# Expected (1, 2, 1)
# Got : TypeError : func() got multiple values for argument 'b'
```

After inspection, I believe this is due to `partial` modifying the signature of `func` by changing `b` and  all subsequent  args to "keyword-only". Which `wraps` doesn't see as it builds the new function from `func` directly.

```
from inspect import signature

signature(partial(func, b=2)) # <Signature (a, *, b=2, c=1)>
signature(newf, follow_wrapped) # <Signature (a, b=1, c=1)>
``` 

So. First of all, is this a bug of `boltons.funcutils.wraps` or is this just not in the scope of what it is supposed to do?
I could use `functools.update_wrapper` but I really wanted to "inject" the new keywords, make them disappear from the signature. Also, using `help()` on a "updated" partial doesn't work, while it does on  `wraps(func)(partial(func))`.

I quickly modified `wraps` and was able to obtain the wanted behaviour by passing the partial func directly and using that one to create the underlying `FunctionBuilder` instance. It works since the arguments are the same, just with modified properties and defaults, but it kinda doesn't make sense since `wraps` is supposed to be a decorator. Thus a solution I see is to move the code from `wraps` to a new  `update_wrapper`. Example:

```
def update_wrapper(wrapper, wrapped, build_from=None, injected=None, expected=None, **kw):
     ... same code as `wraps` but:
     fb = FunctionBuilder.from_func(build_from or wrapped)
     ... and it returns "fully_wrapped"

def wraps(func, injected=None, expected=None, **kw):
    def wrapper_wrapper(wrapper_func):
        return update_wrapper(wrapper_func, func, injected=injected, expected=expected, **kw)
    return wrapper_wrapper
```

What do you think?
(If needed or wanted, I have time to suggest a PR on this)
funcutils.wraps() not working with partials
issue
created
mahmoud
username_1
Very interesting, @username_0! Your desired behavior is definitely up `FunctionBuilder`'s alley. 

I'm not sure how great FunctionBuilder is at working with partials, in part because partials aren't really functions, per se. But maybe we can learn from how signature interprets them and make FunctionBuilder work better!

While your example doesn't show this, I gather your workaround fix uses `build_from` to help FunctionBuilder cope with partials. Could we adopt a more partial-specific approach, either through enhancing `from_func` to check for partials or adding a `from_partial` alternate constructor?
comment
created
aulemahal
username_0
From my experience, `FunctionBuilder` works correctly with partials!  In my example fix, it's `wraps` that needs to be modified, so it builds the new function based on the partial.
In fact, I believe the problem comes from the fact that a "partial" acts as function that has the same keyword arguments than the wrapped function, but with different "kinds": some are now "keyword-only".  
I think, the main culprit is the invocation string, down in `wraps`. If the new func is built from the original one, the invocation string uses positional arguments. But the wrapped partial as those same arguments as keyword-only.
comment
created
mahmoud
username_1
Oh weird. So FunctionBuilder is behaving as expected, and the problem is actually the changes made by `partial` itself?
comment
created
aulemahal
username_0
Almost!
I'm sorry I forgot about a detail in my last comment : `FunctionBuilder` fails with a pure `partial`, but I have the habit of updating the partial with `functools.update_wrapper`.
Ex:
```python
from functools import partial, update_wrapper
from boltons.funcutils import FunctionBuilder

def func(a, b=1, c=1):
    """Docstring"""
    return a, b, c

pfunc = partial(func, b=2)
FunctionBuilder.from_func(pfunc) # <- Will fail cause pfunc is missing attributes like __name__

pfunc_updated = update_wrapper(pfunc, func)
FunctionBuilder.from_func(pfunc_updated)  # <- Works.
```

So, sorry again, let me re-answer your question. `FunctionBuilder` could be modified to accept partials, it might be a good addition. However, this will not solve my issue, which is that I think `funcutils` is missing a non-decorator version of `wraps` that could build the new function from the _wrapper_ instead of the _wrapped_.
This use case is independent of the partial situation, AFAIK it could be used with any _wrapper_ that redefines default values and if args are keyword-only or not. `partial` would be the main use case, but not the only one.
comment
created
mahmoud
username_1
Ahh, that's jiving with what I remember. If a non-decorator version of `wraps()` helps you, that sounds like a good feature. Partials support in FunctionBuilder is probably more nuanced, but I'm also open to it, maybe in a separate PR later.

Also, I learned about `functools.update_wrapper`, which I somehow overlooked all this time. :P Thanks!
comment
closed
mahmoud
username_1
issue