From 6bf883e4327b8a0ee334651133e9f8148a6f2dc9 Mon Sep 17 00:00:00 2001 From: David Zuber Date: Sun, 6 Nov 2016 22:00:17 +0000 Subject: [PATCH] Generic validator decorator --- src/reddel_server/provider.py | 63 ++++++++++++++++++++++----------- src/reddel_server/validators.py | 31 ++++++++++++++-- 2 files changed, 71 insertions(+), 23 deletions(-) diff --git a/src/reddel_server/provider.py b/src/reddel_server/provider.py index 8a13314..a25c3ae 100644 --- a/src/reddel_server/provider.py +++ b/src/reddel_server/provider.py @@ -8,14 +8,24 @@ from . import exceptions, utils, validators -__all__ = ["ProviderBase", "Provider", "ChainedProvider", "RedBaronProvider"] +__all__ = ['ProviderBase', 'Provider', 'ChainedProvider', 'RedBaronProvider', + 'redwraps', 'red_src', 'red_type', 'red_validate'] _RED_FUNC_ATTRS = ['validators', 'red'] def redwraps(towrap): - """Use this when creating decorators instead of :func:`functools.wraps`""" + """Use this when creating decorators instead of :func:`functools.wraps` + + Makes sure to transfer special reddel attributes to the wrapped function. + On top of that uses :func:`functools.wraps`. + + :param towrap: the function to wrap + :type towrap: function + :returns: the decorator + :rtype: function + """ def redwraps_dec(func): wrapped = functools.wraps(towrap)(func) for attr in _RED_FUNC_ATTRS: @@ -31,6 +41,8 @@ def red_src(dump=True): :param dump: if True, dump the return value from the wrapped function. Expectes the return type to be a :class:`redbaron.RedBaron` object. :type dump: :class:`boolean` + :returns: the decorator + :rtype: function """ def red_src_dec(func): @redwraps(func) @@ -44,28 +56,37 @@ def wrapped(self, src, *args, **kwargs): return red_src_dec -def red_type(identifier, single=True): - """Create decorator that checks if the root is identified by identifier +def red_validate(validators): + """Create decorator that adds the given validators to the wrapped function - :param identifier: red baron node identifiers - :type identifier: :class:`str` - :param single: expect a single node in the initial node list. Pass on the first node. - :type single: :class:`bool` + :param validators: the validators + :type validators: :class:`validators.ValidatorInterface` """ - def red_type_dec(func): - validator = validators.BaronTypeValidator([identifier], single=single) - + def red_validate_dec(func): @redwraps(func) def wrapped(self, red, *args, **kwargs): - validator(red) - if single: - red = red[0] - return func(self, red, *args, **kwargs) - + transformed = red + for v in validators: + v(red) + transformed = v.transform(transformed) + return func(self, transformed, *args, **kwargs) wrapped.validators = wrapped.validators or [] - wrapped.validators.append(validator) + wrapped.validators.extend(validators) return wrapped - return red_type_dec + return red_validate_dec + + +def red_type(identifiers, single=True): + """Create decorator that checks if the root is identified by identifier + + :param identifiers: red baron node identifiers + :type identifiers: list of :class:`str` + :param single: expect a single node in the initial node list. Pass on the first node. + :type single: :class:`bool` + :returns: the decorator + :rtype: functions + """ + return red_validate([validators.BaronTypeValidator(identifiers, single=single)]) class ProviderBase(object): @@ -212,7 +233,7 @@ def analyze(self, red, deep=2, with_formatting=False): return "\n".join(red.__help__(deep=deep, with_formatting=False)) @red_src() - @red_type("def") + @red_type(["def"]) def rename_arg(self, red, oldname, newname): for arg in red.arguments: if arg.target.value == oldname: @@ -227,7 +248,7 @@ def rename_arg(self, red, oldname, newname): return red @red_src(dump=False) - @red_type("def") + @red_type(["def"]) def get_args(self, red): """Return a list of args.""" args = [] @@ -244,7 +265,7 @@ def get_args(self, red): return args @red_src() - @red_type("def") + @red_type(["def"]) def add_arg(self, red, index, arg): red.arguments.insert(index, arg) return red diff --git a/src/reddel_server/validators.py b/src/reddel_server/validators.py index d1599a4..2175eb0 100644 --- a/src/reddel_server/validators.py +++ b/src/reddel_server/validators.py @@ -2,21 +2,42 @@ """ from __future__ import absolute_import +import abc + +import six + from . import exceptions -__all__ = ['BaronTypeValidator'] +__all__ = ['BaronTypeValidator', 'ValidatorInterface'] -class ValidatorInterface(object): +class ValidatorInterface(six.with_metaclass(abc.ABCMeta, object)): + """Validator interface. + + Override :meth:`ValidatorInterface.__call__`. + """ + @abc.abstractmethod def __call__(self, red): """Validate the given redbaron source + :param red: the source + :type red: :class:`redbaron.RedBaron` :raises: :class:`ValidationError` """ pass + def transform(self, red): + """Transform the given red baron + + :param red: a red baron or other nodes + """ + return red + class BaronTypeValidator(ValidatorInterface): + """Validate that the given :class:`redbaron.RedBaron` + contains the correct nodes and optionally that there is only a single root node. + """ def __init__(self, identifiers, single=False): """Initialize the validator @@ -42,3 +63,9 @@ def __call__(self, red): if not any(i in identifiers for i in self.identifiers): raise exceptions.ValidationError("Expected identifier %s but got %s" % (self.identifiers, identifiers)) + + def transform(self, red): + """If :data:`BaronTypeValidator.single` is True return the first node.""" + if self.single: + return red[0] + return red