Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix issue not allowing validate_call decorator to be dynamically assigned to a class method. #8249

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
5 changes: 5 additions & 0 deletions pydantic/_internal/_validate_call.py
Expand Up @@ -118,6 +118,11 @@ 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:
# It's possible this wrapper is dynamically applied to a class attribute not allowing
# name to be populated by __set_name__. In this case, we'll manually acquire the name
# from the function reference.
if self._name is None:
self._name = self.raw_function.__name__
try:
# Handle the case where a method is accessed as a class attribute
return objtype.__getattribute__(objtype, self._name) # type: ignore
Expand Down
12 changes: 12 additions & 0 deletions tests/test_validate_call.py
Expand Up @@ -684,6 +684,18 @@ def test(self, x: int):
assert bar.test('1') == (bar, 1)


def test_dynamic_method_decoration():
class Foo:
def bar(self, value: str) -> str:
return f'bar-{value}'

Foo.bar = validate_call(Foo.bar)
assert Foo.bar

foo = Foo()
assert foo.bar('test') == 'bar-test'


@pytest.mark.parametrize('decorator', [staticmethod, classmethod])
def test_classmethod_order_error(decorator):
name = decorator.__name__
Expand Down