Repository: celery/celery

Issue:
Incorrect handling of tasks with keyword arguments
Probably the problem is this: https://github.com/celery/celery/pull/3658

## Checklist

- [x] I have included the output of ``celery -A proj report`` in the issue.
      (if you are not able to do this, then at least specify the Celery
       version affected).
- [x] I have verified that the issue exists against the `master` branch of Celery.

## Steps to reproduce

### celery report
```
software -> celery:4.0.1 (latentcall) kombu:4.0.1 py:3.5.2
            billiard:3.5.0.2 memory:N/A
platform -> system:Linux arch:64bit, ELF imp:CPython
loader   -> celery.loaders.app.AppLoader
settings -> transport:memory results:cache+memory:///

result_backend: 'cache+memory:///'
broker_url: 'memory://localhost//'
```

### Python code

```
from celery import Celery

app = Celery(broker="memory://", backend="cache+memory://")

@app.task
def test_func(foo, *args, bar=''):
    return foo
```

### Reproduce with
```
res = test_func.delay("")
```

## Expected behavior

AsyncResult should be returned

## Actual behavior

```
>>> test_func.delay("")
Traceback (most recent call last):
  File "_confidential_.env/lib/python3.5/site-packages/celery/local.py", line 317, in _get_current_object
    return object.__getattribute__(self, '__thing')
AttributeError: 'PromiseProxy' object has no attribute '__thing'

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "_confidential_.env/lib/python3.5/site-packages/celery/local.py", line 146, in __getattr__
    return getattr(self._get_current_object(), name)
  File "_confidential_.env/lib/python3.5/site-packages/celery/local.py", line 319, in _get_current_object
    return self.__evaluate__()
  File "_confidential_.env/lib/python3.5/site-packages/celery/local.py", line 349, in __evaluate__
    thing = Proxy._get_current_object(self)
  File "_confidential_.env/lib/python3.5/site-packages/celery/local.py", line 109, in _get_current_object
    return loc(*self.__args, **self.__kwargs)
  File "_confidential_.env/lib/python3.5/site-packages/celery/app/base.py", line 453, in _task_from_fun
    '__header__': staticmethod(head_from_fun(fun, bound=bind)),
  File "_confidential_.env/lib/python3.5/site-packages/celery/utils/functional.py", line 281, in head_from_fun
    exec(definition, namespace)
  File "<string>", line 2
    def test_func(foo, *args, *, bar="0"):
                              ^
SyntaxError: invalid syntax
```


Discussion:
probably the fix is to change https://github.com/celery/celery/blob/master/celery/utils/functional.py#L249 to the following:

```python
    return ', '.join(filter(None, [
        ', '.join(positional),
        ', '.join('{0}={1}'.format(k, v) for k, v in optional),
        '*{0}'.format(spec.varargs) if spec.varargs else None,
        '**{0}'.format(spec.varkw) if spec.varkw else None,
        '*' if (kwonlyargs or kwonlyargs_optional) and not spec.varargs else None, # this is the changed line
        ', '.join(kwonlyargs) if kwonlyargs else None,
        ', '.join('{0}="{1}"'.format(k, v) for k, v in kwonlyargs_optional),
    ]))
```

Resolving patch:
diff --git a/celery/utils/functional.py b/celery/utils/functional.py
--- a/celery/utils/functional.py
+++ b/celery/utils/functional.py
@@ -252,10 +252,10 @@ def _argsfromspec(spec, replace_defaults=True):
         ', '.join(positional),
         ', '.join('{0}={1}'.format(k, v) for k, v in optional),
         '*{0}'.format(varargs) if varargs else None,
-        '**{0}'.format(varkw) if varkw else None,
         '*' if (kwonlyargs or kwonlyargs_optional) and not varargs else None,
         ', '.join(kwonlyargs) if kwonlyargs else None,
         ', '.join('{0}="{1}"'.format(k, v) for k, v in kwonlyargs_optional),
+        '**{0}'.format(varkw) if varkw else None,
     ]))
 
 


Test patch:
diff --git a/t/unit/utils/test_functional.py b/t/unit/utils/test_functional.py
--- a/t/unit/utils/test_functional.py
+++ b/t/unit/utils/test_functional.py
@@ -165,7 +165,7 @@ def f(x, y, kwarg=1):
     @skip.unless_python3()
     def test_regression_3678(self):
         local = {}
-        fun = ('def f(foo, *args, bar=""):'
+        fun = ('def f(foo, *args, bar="", **kwargs):'
                '    return foo, args, bar')
         exec(fun, {}, local)