Support instance methods and class methods with @validate_arguments (#1272)

This commit is contained in:
Samuel Colvin
2020-03-17 20:12:30 +00:00
committed by GitHub
parent d8262131d2
commit e57f777c4d
3 changed files with 59 additions and 7 deletions
+1
View File
@@ -0,0 +1 @@
Support instance methods and class methods with `@validate_arguments`
+11 -4
View File
@@ -1,4 +1,4 @@
from functools import update_wrapper
from functools import wraps
from typing import TYPE_CHECKING, Any, Dict, List, Mapping, Tuple, TypeVar, cast, get_type_hints
from . import validator
@@ -19,8 +19,15 @@ def validate_arguments(function: 'Callable') -> 'Callable':
Decorator to validate the arguments passed to a function.
"""
vd = ValidatedFunction(function)
vd = update_wrapper(vd, function) # type: ignore
return cast('Callable', vd)
@wraps(function)
def wrapper_function(*args: Any, **kwargs: Any) -> Any:
return vd.call(*args, **kwargs)
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)
ALT_V_ARGS = 'v__args'
@@ -95,7 +102,7 @@ class ValidatedFunction:
self.create_model(fields, takes_args, takes_kwargs)
def __call__(self, *args: Any, **kwargs: Any) -> Any:
def call(self, *args: Any, **kwargs: Any) -> Any:
values = self.build_values(args, kwargs)
m = self.model(**values)
return self.execute(m)
+47 -3
View File
@@ -63,10 +63,10 @@ def test_wrap():
assert foo_bar.__name__ == 'foo_bar'
assert foo_bar.__module__ == 'tests.test_decorator'
assert foo_bar.__qualname__ == 'test_wrap.<locals>.foo_bar'
assert isinstance(foo_bar, ValidatedFunction)
assert isinstance(foo_bar.vd, ValidatedFunction)
assert callable(foo_bar.raw_function)
assert foo_bar.arg_mapping == {0: 'a', 1: 'b'}
assert foo_bar.positional_only_args == set()
assert foo_bar.vd.arg_mapping == {0: 'a', 1: 'b'}
assert foo_bar.vd.positional_only_args == set()
assert issubclass(foo_bar.model, BaseModel)
assert foo_bar.model.__fields__.keys() == {'a', 'b', 'args', 'kwargs'}
assert foo_bar.model.__name__ == 'FooBar'
@@ -218,3 +218,47 @@ def test_string_annotation():
{'loc': ('a', 0), 'msg': 'value is not a valid integer', 'type': 'type_error.integer'},
{'loc': ('b',), 'msg': 'field required', 'type': 'value_error.missing'},
]
def test_item_method():
class X:
def __init__(self, v):
self.v = v
@validate_arguments
def foo(self, a: int, b: int):
assert self.v == a
return f'{a}, {b}'
x = X(4)
assert x.foo(4, 2) == '4, 2'
assert x.foo(*[4, 2]) == '4, 2'
with pytest.raises(ValidationError) as exc_info:
x.foo()
assert exc_info.value.errors() == [
{'loc': ('a',), 'msg': 'field required', 'type': 'value_error.missing'},
{'loc': ('b',), 'msg': 'field required', 'type': 'value_error.missing'},
]
def test_class_method():
class X:
@classmethod
@validate_arguments
def foo(cls, a: int, b: int):
assert cls == X
return f'{a}, {b}'
x = X()
assert x.foo(4, 2) == '4, 2'
assert x.foo(*[4, 2]) == '4, 2'
with pytest.raises(ValidationError) as exc_info:
x.foo()
assert exc_info.value.errors() == [
{'loc': ('a',), 'msg': 'field required', 'type': 'value_error.missing'},
{'loc': ('b',), 'msg': 'field required', 'type': 'value_error.missing'},
]