Skip to content
guyskk edited this page Mar 29, 2020 · 5 revisions

Custom Validator

validator() decorater is used to create validator, and it can make you validator support common parameters default, optional, desc, invalid_to, invalid_to_default and optional object.

You can control the validor accept and output value types by accept and output parameter:

  • accept (str | object | (str,object)):
    • str: the validator accept only string, treat both None and empty string as None
    • object: the validator accept only object
    • (str,object): (default) the validator accept both string and object, treat both None and empty string as None
  • output (str | object | (str,object)):
    • str: (default) the validator always output string, convert None to empty string
    • object: the validator always output object
    • (str, object): the validator can output both string and object, and has an object parameter to control which to output. To reduce conflict, the object parameter will rename to output_object in validator's signature.

A general custom validator:

from validr import T, validator

@validator(accept=(str, object), output=(str, object))
def xxx_validator(compiler, items=None, output_object=False, some_param=None):
    """Custom validator

    Params:
        compiler: can be used for compile inner schema
        items: optional, and can only be scalar type, passed by schema in `T.validator(items)` form
        output_object: control whether output object, only passed when `output=(str, object)`
        some_param: other params
    Returns:
        validate function
    """
    def validate(value):
        """Validate function

        Params:
            value: data to be validate
        Returns:
            valid value or converted value
        Raises:
            Invalid: value invalid
        """
        try:
            # validate/convert the value
        except Exception:
            # raise Invalid('invalid xxx')
        if output_object:
            # return python object
        else:
            # return string
    return validate

# use custom validators
compiler = Compiler(validators={
    # name: validator
    'xxx': xxx_validator,
})

Example, time interval validator:

from validr import T, validator, SchemaError, Invalid, Compiler

UNITS = {'s':1, 'm':60, 'h':60*60, 'd':24*60*60}

def to_seconds(t):
    return int(t[:-1]) * UNITS[t[-1]]

@validator(accept=str, output=object)
def interval_validator(compiler, min='0s', max='365d'):
    """Time interval validator, convert value to seconds

    Supported time units:
        s: seconds, eg: 10s
        m: minutes, eg: 10m
        h: hours, eg: 1h
        d: days, eg: 7d
    """
    try:
        min = to_seconds(min)
    except (IndexError,KeyError,ValueError):
        raise SchemaError('invalid min value') from None
    try:
        max = to_seconds(max)
    except (IndexError,KeyError,ValueError):
        raise SchemaError('invalid max value') from None
    def validate(value):
        try:
            value = to_seconds(value)
        except (IndexError,KeyError,ValueError):
            raise Invalid("invalid interval") from None
        if value < min:
            raise Invalid("interval must >= {} seconds".format(min))
        if value > max:
            raise Invalid("interval must <= {} seconds".format(max))
        return value
    return validate

compiler = Compiler(validators={"interval": interval_validator})
f = compiler.compile(T.interval.max('8h'))
>>> f('15m')
900
>>> f('15x')
...
validr._exception.Invalid: invalid interval, value=15x
>>> f('10h')
...
validr._exception.Invalid: interval must <= 28800 seconds, value=10h
>>> f = compiler.compile(T.interval.default('5m'))
>>> f(None)
300
>>> compiler.compile(T.interval.max('12x'))
...
validr._exception.SchemaError: invalid max value, schema=interval.max('12x')

Create regex validator:

from validr import T, Compiler, create_re_validator

regex_time = r'([01]?\d|2[0-3]):[0-5]?\d:[0-5]?\d'
time_validator = create_re_validator("time", regex_time)
compiler = Compiler(validators={"time": time_validator})
f = compiler.compile(T.time.default("00:00:00"))

>>> f("12:00:00")
'12:00:00'
>>> f("12:00:123")
...
validr._exception.Invalid: invalid time, value=12:00:123
>>> f(None)
'00:00:00'
>>>