Repository: pydantic/pydantic

Issue:
AttributeError in class attribute access for validate_call
### Initial Checks

- [X] I confirm that I'm using Pydantic V2 installed directly from the `main` branch, or equivalent

### Description

https://github.com/pydantic/pydantic/pull/6406 introduced an AttributeError when accessing a method on a class (not instance):

```
 draft @ pip-run 'git+https://github.com/pydantic/pydantic' -- pyd2.py
Traceback (most recent call last):
  File "/Users/jaraco/draft/pyd2.py", line 14, in <module>
    assert Thing.c == Thing.c
           ^^^^^^^
  File "/var/folders/sx/n5gkrgfx6zd91ymxr2sr9wvw00n8zm/T/pip-run-o94_k6_k/pydantic/_internal/_validate_call.py", line 101, in __get__
    setattr(obj, self._name, result)
AttributeError: 'NoneType' object has no attribute 'c'
```

### Example Code

```Python
from pydantic import validate_call


class Thing:
    def a(self):
        pass

    c = validate_call(a)


thing = Thing()
assert thing.a == thing.a
assert thing.c == thing.c, f'{thing.c} != {thing.c}'
assert Thing.c == Thing.c
```


### Python, Pydantic & OS Version

```Text
draft @ pip-run 'git+https://github.com/pydantic/pydantic' -- -c 'import pydantic.version; print(pydantic.version.version_info())'
             pydantic version: 2.0
        pydantic-core version: 2.0.2 release build profile
                 install path: /private/var/folders/sx/n5gkrgfx6zd91ymxr2sr9wvw00n8zm/T/pip-run-hek15lsq/pydantic
               python version: 3.11.4 (main, Jun 15 2023, 07:55:38) [Clang 14.0.3 (clang-1403.0.22.14.1)]
                     platform: macOS-13.4.1-arm64-arm-64bit
     optional deps. installed: ['typing-extensions']
```


Selected Assignee: @Kludex


Discussion:
Tagging @dmontagu , author of the implicated change.
We are working on this. 👍 
Damn, thanks @jaraco. @Kludex let's make sure we add a test for classmethod/staticmethod as well

Resolving patch:
diff --git a/pydantic/_internal/_validate_call.py b/pydantic/_internal/_validate_call.py
--- a/pydantic/_internal/_validate_call.py
+++ b/pydantic/_internal/_validate_call.py
@@ -95,10 +95,21 @@ def __call__(self, *args: Any, **kwargs: Any) -> Any:
 
     def __get__(self, obj: Any, objtype: type[Any] | None = None) -> ValidateCallWrapper:
         """Bind the raw function and return another ValidateCallWrapper wrapping that."""
+        if obj is None:
+            try:
+                # Handle the case where a method is accessed as a class attribute
+                return objtype.__getattribute__(objtype, self._name)  # type: ignore
+            except AttributeError:
+                # This will happen the first time the attribute is accessed
+                pass
+
         bound_function = self.raw_function.__get__(obj, objtype)
         result = self.__class__(bound_function, self._config, self._validate_return)
         if self._name is not None:
-            setattr(obj, self._name, result)
+            if obj is not None:
+                setattr(obj, self._name, result)
+            else:
+                setattr(objtype, self._name, result)
         return result
 
     def __set_name__(self, owner: Any, name: str) -> None:


Test patch:
diff --git a/tests/test_validate_call.py b/tests/test_validate_call.py
--- a/tests/test_validate_call.py
+++ b/tests/test_validate_call.py
@@ -653,11 +653,19 @@ def __repr__(self) -> str:
 
 def test_methods_are_not_rebound():
     class Thing:
-        def a(self):
-            pass
+        def __init__(self, x: int):
+            self.x = x
+
+        def a(self, x: int):
+            return x + self.x
 
         c = validate_call(a)
 
-    thing = Thing()
+    thing = Thing(1)
     assert thing.a == thing.a
     assert thing.c == thing.c
+    assert Thing.c == Thing.c
+
+    # Ensure validation is still happening
+    assert Thing.c(thing, '2') == 3
+    assert Thing(2).c('3') == 5