Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 32 additions & 5 deletions yamale/syntax/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,6 @@

from .. import validators as val

safe_globals = ("True", "False", "None")
safe_builtins = dict((f, __builtins__[f]) for f in safe_globals)


def _validate_expr(call_node, validators):
# Validate that the expression uses a known, registered validator.
Expand All @@ -28,12 +25,42 @@ def _validate_expr(call_node, validators):
raise SyntaxError("Argument values must either be constant literals, or else " "reference other validators.")


def _eval_literal(node, validators):
if isinstance(node, ast.Constant):
return node.value
if isinstance(node, ast.Name) and node.id in validators:
return validators[node.id]
if isinstance(node, ast.UnaryOp) and isinstance(node.operand, ast.Constant):
value = node.operand.value
if isinstance(node.op, ast.UAdd):
return +value
if isinstance(node.op, ast.USub):
return -value
if isinstance(node.op, ast.Not):
return not value
if isinstance(node.op, ast.Invert):
return ~value
if isinstance(node, ast.Call):
return _construct_validator(node, validators)
raise SyntaxError("Argument values must either be constant literals, or else reference other validators.")


def _construct_validator(call_node, validators):
func_name = call_node.func.id
args = [_eval_literal(arg, validators) for arg in call_node.args]
kwargs = {}
for keyword in call_node.keywords:
if keyword.arg is None:
raise SyntaxError("Keyword argument unpacking is not supported.")
kwargs[keyword.arg] = _eval_literal(keyword.value, validators)
return validators[func_name](*args, **kwargs)


def parse(validator_string, validators=None):
validators = validators or val.DefaultValidators
try:
tree = ast.parse(validator_string, mode="eval")
_validate_expr(tree.body, validators)
# evaluate with access to a limited global scope only
return eval(compile(tree, "<ast>", "eval"), {"__builtins__": safe_builtins}, validators)
return _construct_validator(tree.body, validators)
except (SyntaxError, NameError, TypeError) as e:
raise SyntaxError("Invalid schema expression: '%s'. " % validator_string + str(e))
28 changes: 11 additions & 17 deletions yamale/syntax/tests/test_parser.py
Original file line number Diff line number Diff line change
@@ -1,23 +1,7 @@
from pytest import raises

from .. import parser as par
from yamale.validators.validators import (
Validator,
String,
Regex,
Number,
Integer,
Boolean,
List,
Day,
Timestamp,
Ip,
Mac,
)


def test_eval():
assert eval("String()") == String()
from yamale.validators.validators import Validator, String, Regex, Number, Integer, Boolean, List, Day, Timestamp, Ip, Mac


def test_types():
Expand All @@ -34,6 +18,16 @@ def test_types():
assert par.parse("mac()") == Mac()


def test_nested_type_reference():
parsed = par.parse("list(list(num, min=2, max=2), min=2, max=2)")
assert parsed == List(List(Number, min=2, max=2), min=2, max=2)


def test_unary_constants():
parsed = par.parse("num(min=-1, max=+1)")
assert parsed == Number(min=-1, max=1)


def test_custom_type():
class my_validator(Validator):
pass
Expand Down