Repository: pydantic/pydantic

Issue:
mypy "error: <nothing> not callable" when passing config to validate_arguments
# Bug

Output of `python -c "import pydantic.utils; print(pydantic.utils.version_info())"`:
```
             pydantic version: 1.7
            pydantic compiled: True
                 install path: /home/brian/repos/medigator/venv/lib/python3.6/site-packages/pydantic
               python version: 3.6.9 (default, Oct  8 2020, 12:12:24)  [GCC 8.4.0]
                     platform: Linux-4.15.0-121-generic-x86_64-with-Ubuntu-18.04-bionic
     optional deps. installed: ['typing-extensions', 'email-validator']
```

The example code from the docs demonstrating the use of passing a config to `validate_arguments` causes an error when running mypy (version 0.790):

```py
from pydantic import ValidationError, validate_arguments


class Foobar:
    def __init__(self, v: str):
        self.v = v

    def __add__(self, other: 'Foobar') -> str:
        return f'{self} + {other}'

    def __str__(self) -> str:
        return f'Foobar({self.v})'


@validate_arguments(config=dict(arbitrary_types_allowed=True))
def add_foobars(a: Foobar, b: Foobar):
    return a + b
```

```
test.py:15: error: <nothing> not callable
```

If I remove the `config` parameter but still call `validate_arguments` it still occurs.
If I remove the call and just decorate with `@validate_arguments` then mypy is happy.

I assume it has something to do with the type annotation of `validate_arguments`.
mypy "error: <nothing> not callable" when passing config to validate_arguments
# Bug

Output of `python -c "import pydantic.utils; print(pydantic.utils.version_info())"`:
```
             pydantic version: 1.7
            pydantic compiled: True
                 install path: /home/brian/repos/medigator/venv/lib/python3.6/site-packages/pydantic
               python version: 3.6.9 (default, Oct  8 2020, 12:12:24)  [GCC 8.4.0]
                     platform: Linux-4.15.0-121-generic-x86_64-with-Ubuntu-18.04-bionic
     optional deps. installed: ['typing-extensions', 'email-validator']
```

The example code from the docs demonstrating the use of passing a config to `validate_arguments` causes an error when running mypy (version 0.790):

```py
from pydantic import ValidationError, validate_arguments


class Foobar:
    def __init__(self, v: str):
        self.v = v

    def __add__(self, other: 'Foobar') -> str:
        return f'{self} + {other}'

    def __str__(self) -> str:
        return f'Foobar({self.v})'


@validate_arguments(config=dict(arbitrary_types_allowed=True))
def add_foobars(a: Foobar, b: Foobar):
    return a + b
```

```
test.py:15: error: <nothing> not callable
```

If I remove the `config` parameter but still call `validate_arguments` it still occurs.
If I remove the call and just decorate with `@validate_arguments` then mypy is happy.

I assume it has something to do with the type annotation of `validate_arguments`.


Discussion:

Resolving patch:
diff --git a/pydantic/decorator.py b/pydantic/decorator.py
--- a/pydantic/decorator.py
+++ b/pydantic/decorator.py
@@ -1,5 +1,19 @@
 from functools import wraps
-from typing import TYPE_CHECKING, Any, Dict, List, Mapping, Tuple, Type, TypeVar, Union, cast, get_type_hints
+from typing import (
+    TYPE_CHECKING,
+    Any,
+    Callable,
+    Dict,
+    List,
+    Mapping,
+    Optional,
+    Tuple,
+    Type,
+    TypeVar,
+    Union,
+    get_type_hints,
+    overload,
+)
 
 from . import validator
 from .errors import ConfigError
@@ -11,16 +25,26 @@
 if TYPE_CHECKING:
     from .typing import AnyCallable
 
-    Callable = TypeVar('Callable', bound=AnyCallable)
+    AnyCallableT = TypeVar('AnyCallableT', bound=AnyCallable)
     ConfigType = Union[None, Type[Any], Dict[str, Any]]
 
 
-def validate_arguments(func: 'Callable' = None, *, config: 'ConfigType' = None) -> 'Callable':
+@overload
+def validate_arguments(func: None = None, *, config: 'ConfigType' = None) -> Callable[['AnyCallableT'], 'AnyCallableT']:
+    ...
+
+
+@overload
+def validate_arguments(func: 'AnyCallableT') -> 'AnyCallableT':
+    ...
+
+
+def validate_arguments(func: Optional['AnyCallableT'] = None, *, config: 'ConfigType' = None) -> Any:
     """
     Decorator to validate the arguments passed to a function.
     """
 
-    def validate(_func: 'Callable') -> 'Callable':
+    def validate(_func: 'AnyCallable') -> 'AnyCallable':
         vd = ValidatedFunction(_func, config)
 
         @wraps(_func)
@@ -30,12 +54,12 @@ def wrapper_function(*args: Any, **kwargs: Any) -> Any:
         wrapper_function.vd = vd  # type: ignore
         wrapper_function.raw_function = vd.raw_function  # type: ignore
         wrapper_function.model = vd.model  # type: ignore
-        return cast('Callable', wrapper_function)
+        return wrapper_function
 
     if func:
         return validate(func)
     else:
-        return cast('Callable', validate)
+        return validate
 
 
 ALT_V_ARGS = 'v__args'
@@ -44,7 +68,7 @@ def wrapper_function(*args: Any, **kwargs: Any) -> Any:
 
 
 class ValidatedFunction:
-    def __init__(self, function: 'Callable', config: 'ConfigType'):  # noqa C901
+    def __init__(self, function: 'AnyCallableT', config: 'ConfigType'):  # noqa C901
         from inspect import Parameter, signature
 
         parameters: Mapping[str, Parameter] = signature(function).parameters


Test patch:
diff --git a/tests/mypy/modules/success.py b/tests/mypy/modules/success.py
--- a/tests/mypy/modules/success.py
+++ b/tests/mypy/modules/success.py
@@ -121,6 +121,7 @@ class WithField(BaseModel):
     first_name: str = Field('John', const=True)
 
 
+# simple decorator
 @validate_arguments
 def foo(a: int, *, c: str = 'x') -> str:
     return c * a
@@ -130,6 +131,16 @@ def foo(a: int, *, c: str = 'x') -> str:
 foo(1)
 
 
+# nested decorator should not produce an error
+@validate_arguments(config={'arbitrary_types_allowed': True})
+def bar(a: int, *, c: str = 'x') -> str:
+    return c * a
+
+
+bar(1, c='thing')
+bar(1)
+
+
 class Foo(BaseModel):
     a: int