summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlec Thomas <alec@swapoff.org>2016-09-20 07:46:46 +1000
committerGitHub <noreply@github.com>2016-09-20 07:46:46 +1000
commit6802ce06fd77c3b6bc0ef453f06441e8573330f6 (patch)
tree1d51f18ee1b4e5a67e5afd92cc8cf7b87e688df8
parent7386b1d94e2ab2121266ef6f2b35b940c9e216fa (diff)
parent853244511b1bf92ea3a86edb50addc8aa7058f57 (diff)
downloadvoluptuous-6802ce06fd77c3b6bc0ef453f06441e8573330f6.tar.gz
Merge pull request #185 from ngergo/feature-argument-validation-decorator
Argument validation decorator
-rw-r--r--voluptuous/schema_builder.py76
-rw-r--r--voluptuous/tests/tests.py90
2 files changed, 147 insertions, 19 deletions
diff --git a/voluptuous/schema_builder.py b/voluptuous/schema_builder.py
index 5f686c9..8f93863 100644
--- a/voluptuous/schema_builder.py
+++ b/voluptuous/schema_builder.py
@@ -1018,15 +1018,71 @@ def message(default=None, cls=None):
return decorator
-def validate_schema(*a, **kw):
- schema = Schema(*a, **kw)
+def _args_to_dict(func, args):
+ """Returns argument names as values as key-value pairs."""
+ if sys.version_info >= (3, 0):
+ arg_count = func.__code__.co_argcount
+ arg_names = func.__code__.co_varnames[:arg_count]
+ else:
+ arg_count = func.func_code.co_argcount
+ arg_names = func.func_code.co_varnames[:arg_count]
- def decorator(f):
- @wraps(f)
- def wrapper(*args, **kwargs):
- result = f(*args, **kwargs)
- schema(result)
- return result
- return wrapper
+ arg_value_list = list(args)
+ arguments = dict((arg_name, arg_value_list[i])
+ for i, arg_name in enumerate(arg_names)
+ if i < len(arg_value_list))
+ return arguments
- return decorator
+
+def _merge_args_with_kwargs(args_dict, kwargs_dict):
+ """Merge args with kwargs."""
+ ret = args_dict.copy()
+ ret.update(kwargs_dict)
+ return ret
+
+
+def validate(*a, **kw):
+ """Decorator for validating arguments of a function against a given schema.
+
+ Set restrictions for arguments:
+
+ >>> @validate(arg1=int, arg2=int)
+ ... def foo(arg1, arg2):
+ ... return arg1 * arg2
+
+ Set restriction for returned value:
+
+ >>> @validate(arg=int, __return__=int)
+ ... def foo(arg1):
+ ... return arg1 * 2
+
+ """
+ RETURNS_KEY = '__return__'
+
+ def validate_schema_decorator(func):
+
+ returns_defined = False
+ returns = None
+
+ schema_args_dict = _args_to_dict(func, a)
+ schema_arguments = _merge_args_with_kwargs(schema_args_dict, kw)
+
+ if RETURNS_KEY in schema_arguments:
+ returns_defined = True
+ returns = schema_arguments[RETURNS_KEY]
+ del schema_arguments[RETURNS_KEY]
+
+ input_schema = Schema(schema_arguments) if len(schema_arguments) != 0 else lambda x: x
+ output_schema = Schema(returns) if returns_defined else lambda x: x
+
+ @wraps(func)
+ def func_wrapper(*args, **kwargs):
+ args_dict = _args_to_dict(func, args)
+ arguments = _merge_args_with_kwargs(args_dict, kwargs)
+ validated_arguments = input_schema(arguments)
+ output = func(**validated_arguments)
+ return output_schema(output)
+
+ return func_wrapper
+
+ return validate_schema_decorator
diff --git a/voluptuous/tests/tests.py b/voluptuous/tests/tests.py
index 79afa49..86a43ce 100644
--- a/voluptuous/tests/tests.py
+++ b/voluptuous/tests/tests.py
@@ -5,7 +5,7 @@ from voluptuous import (
Schema, Required, Extra, Invalid, In, Remove, Literal,
Url, MultipleInvalid, LiteralInvalid, NotIn, Match, Email,
Replace, Range, Coerce, All, Any, Length, FqdnUrl, ALLOW_EXTRA, PREVENT_EXTRA,
- validate_schema, ExactSequence, Equal, Unordered
+ validate, ExactSequence, Equal, Unordered
)
from voluptuous.humanize import humanize_error
@@ -423,14 +423,6 @@ def test_fix_157():
assert_raises(MultipleInvalid, s, ['four'])
-def test_schema_decorator():
- @validate_schema(int)
- def fn(arg):
- return arg
-
- fn(1)
- assert_raises(Invalid, fn, 1.0)
-
def test_range_exlcudes_nan():
s = Schema(Range(min=0, max=10))
@@ -485,3 +477,83 @@ def test_empty_list_as_exact():
s = Schema([])
assert_raises(Invalid, s, [1])
s([])
+
+
+def test_schema_decorator_match_with_args():
+ @validate(int)
+ def fn(arg):
+ return arg
+
+ fn(1)
+
+
+def test_schema_decorator_unmatch_with_args():
+ @validate(int)
+ def fn(arg):
+ return arg
+
+ assert_raises(Invalid, fn, 1.0)
+
+
+def test_schema_decorator_match_with_kwargs():
+ @validate(arg=int)
+ def fn(arg):
+ return arg
+
+ fn(1)
+
+
+def test_schema_decorator_unmatch_with_kwargs():
+ @validate(arg=int)
+ def fn(arg):
+ return arg
+
+ assert_raises(Invalid, fn, 1.0)
+
+
+def test_schema_decorator_match_return_with_args():
+ @validate(int, __return__=int)
+ def fn(arg):
+ return arg
+
+ fn(1)
+
+
+def test_schema_decorator_unmatch_return_with_args():
+ @validate(int, __return__=int)
+ def fn(arg):
+ return "hello"
+
+ assert_raises(Invalid, fn, 1)
+
+
+def test_schema_decorator_match_return_with_kwargs():
+ @validate(arg=int, __return__=int)
+ def fn(arg):
+ return arg
+
+ fn(1)
+
+
+def test_schema_decorator_unmatch_return_with_kwargs():
+ @validate(arg=int, __return__=int)
+ def fn(arg):
+ return "hello"
+
+ assert_raises(Invalid, fn, 1)
+
+
+def test_schema_decorator_return_only_match():
+ @validate(__return__=int)
+ def fn(arg):
+ return arg
+
+ fn(1)
+
+
+def test_schema_decorator_return_only_unmatch():
+ @validate(__return__=int)
+ def fn(arg):
+ return "hello"
+
+ assert_raises(Invalid, fn, 1)