Skip to content

Commit

Permalink
Generic validator decorator
Browse files Browse the repository at this point in the history
  • Loading branch information
storax committed Nov 6, 2016
1 parent 220bf80 commit 6bf883e
Show file tree
Hide file tree
Showing 2 changed files with 71 additions and 23 deletions.
63 changes: 42 additions & 21 deletions src/reddel_server/provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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)
Expand All @@ -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):
Expand Down Expand Up @@ -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:
Expand All @@ -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 = []
Expand All @@ -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
31 changes: 29 additions & 2 deletions src/reddel_server/validators.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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

0 comments on commit 6bf883e

Please sign in to comment.