findig.tools.validator — Request input validators

The findig.tools.validator module exposes the Validator which can be used to validate an application or request’s input data.

Validators work by specifying a converter for each field in the input data to be validated:

validator = Validator(app)

@validator.enforce(id=int)
@app.route("/test")
def resource():
    pass

@resource.model("write")
def write_resource(data):
    assert isinstance(data['id'], int)

If the converter fails to convert the field’s value, then a 400 BAD REQUEST error is sent back.

Converters don’t have to be functions; they can be a singleton list containing another converter, indicating that the field is expected to be a list of items for which that converter works:

@validator.enforce(ids=[int])
@app.route("/test2")
def resource2():
    pass

@resource2.model("write")
def write_resource(data):
    for id in data['ids']:
        assert isinstance(id, int)

Converters can also be string specifications corresponding to a pre-registered converter and its arguments. All of werkzeug’s builtin converters and their arguments and their arguments are pre-registered and thus usable:

@validator.enforce(foo='any(bar,baz)', cid='string(length=3)')
@app.route("/test3")
def resource3():
    pass

@resource3.model("write")
def write_resource(data):
    assert data['foo'] in ('bar', 'baz')
    assert len(data['cid']) == 3
exception findig.tools.validator.ValidationFailed[source]

Raised whenever a Validator fails to validate one or more fields.

This exception is a subclass of werkzeug.exceptions.BadRequest, so if allowed to bubble up, findig will send a 400 BAD REQUEST response automatically.

Applications can, however, customize the way this exception is handled:

from werkzeug.wrappers import Response

# This assumes that the app was not supplied a custom error_handler
# function as an argument.
# If a custom error_handler function is being used, then
# do a test for this exception type inside the function body
# and replicate the logic
@app.error_handler.register(ValidationFailed)
def on_validation_failed(e):
    # Construct a response based on the error received
    msg = "Failed to convert input data for the following fields: "
    msg += str(e.fields)
    return Response(msg, status=e.status)
fields

A list of field names for which validation has failed. This will always be a complete list of failed fields.

validator

The Validator that raised the exception.

exception findig.tools.validator.UnexpectedFields(fields, validator)[source]

Bases: findig.tools.validator.ValidationFailed

Raised whenever a resource receives an unexpected input field.

exception findig.tools.validator.MissingFields(fields, validator)[source]

Bases: findig.tools.validator.ValidationFailed

Raised when a resource does not receive a required field in its input.

exception findig.tools.validator.InvalidFields(fields, validator)[source]

Bases: findig.tools.validator.ValidationFailed

Raised when a resource receives a field that the validator can’t convert.

class findig.tools.validator.Validator(app=None, include_collections=True)[source]

Bases: object

A higher-level tool to be used to validate request input data.

Parameters:
  • app (findig.App) – The Findig application that the validator is attached to.
  • include_collections – If True, any validation rules set on any resource will also be used for any Collection that collects it. Even when this argument is set, inherited rules can still be overridden by declaring rules specifically for the collection.

Validators are only capable of validating request input data (i.e., data received as part of the request body). To validate URL fragments, consider using converters in your URL rules. See werkzeug’s routing reference.

Validators work by specifying converters for request input fields. If a converter is specified, the validator will use it to convert the field and replace it with the converted value. See enforce() for more about converters.

attach_to(app)[source]

Hook the validator into a Findig application.

Doing so allows the validator to inspect and replace incoming input data. This is called automatically for an app passed to the validator’s constructor, but can be called for additional app instances. This function should only be called once per application.

Parameters:app (findig.App) – The Findig application that the validator is attached to.
static date(format[, format[, ...]])[source]

Create a function that validates a date field.

Parameters:format – A date/time format according to datetime.datetime.strptime(). If more than one formats are passed in, the generated function will try each format in order until one of them works on the field (or until there are no formats left to try).

Example:

>>> func = Validator.date("%Y-%m-%d %H:%M:%S%z")
>>> func("2015-07-17 09:00:00+0400")
datetime.datetime(2015, 7, 17, 9, 0, tzinfo=datetime.timezone(datetime.timedelta(0, 14400)))
>>> func("not-a-date")
Traceback (most recent call last):
  ...
ValueError: time data 'not-a-date' does not match format '%Y-%m-%d %H:%M:%S%z'

>>> func = Validator.date("%Y-%m-%d %H:%M:%S%z", "%Y-%m-%d")
>>> func("2015-07-17")
datetime.datetime(2015, 7, 17, 0, 0)
enforce(resource, **validation_spec)[source]

Register a validation specification for a resource.

The validation specification is a set of field=converter arguments linking an input field name to a converter that should be used to validate the field. A converter can be any of the following:

  • collections.abc.Callable (including functions) – This can be a simple type such as int or uuid.UUID, but any function or callable can work. It should take a field value and convert it to a value of the desired type. If it throws an error, then findig will raise a BadRequest exception.

    Example:

    # Converts an int from a valid string base 10 representation:
    validator.enforce(resource, game_id=int)
    
    # Converts to a float from a valid string
    validator.enforce(resource, duration=float)
    
  • str – If a string is given, then it is interpreted as a converter specification. A converter specification includes the converter name and optionally arguments for pre-registered converters. The following converters are pre-registered by default (you may notice that they correspond to the URL rule converters available for werkzeug):

    string(minlength=1, length=None, maxlength=None)

    This converter will accept a string.

    Parameters:
    • length – If given, it will indicate a fixed length field.
    • minlength – The minimum allowed length for the field.
    • maxlength – The maximum allowed length for the field.
    any(*items)

    This converter will accept only values from the variable list of options passed as the converter arguments. It’s useful for limiting a field’s value to a small set of possible options.

    int(fixed_digits=0, min=None, max=None)

    This converter will accept a string representation of a non-negative integer.

    Parameters:
    • fixed_digits – The number of fixed digits in the field. For example, set this to 3 to convert '001' but not '1'. The default is a variable number of digits.
    • min – The minimum allowed value for the field.
    • max – The maximum allowed value for the field.
    float(min=None, max=None)

    This converter will accept a string representation of a non-negative floating point number.

    Parameters:
    • min – The minimum allowed value for the field.
    • max – The maximum allowed value for the field.
    uuid()

    This converter will accept a string representation of a uuid and convert it to a uuid.UUID.

    Converters that do not need arguments can omit the parentheses in the converter specification.

    Examples:

    # Converts a 4 character string
    validator.enforce(resource, student_id='string(length=10)')
    
    # Converts any of these string values: 'foo', 1000, True
    validator.enforce(resource, field='any(foo, 1000, True)')
    
    # Converts any non-negative integer
    validator.enforce(resource, game_id='int')
    
    # and any float <1000
    validator.enforce(resource, duration='float(max=1000)')
    

    Important

    Converter specifications in this form cannot match strings that contain forward slashes. For example, ‘string(length=2)’ will fail to match ‘/e’ and ‘any(application/json,html)’ will fail to match ‘application/json’.

  • or, list – This must be a singleton list containing a converter. When this is given, the validator will treat the field like a list and use the converter to convert each item.

    Example:

    # Converts a list of integers
    validator.enforce(resource, games=[int])
    
    # Converts a list of uuids
    validator.enforce(resource, components=['uuid'])
    
    # Converts a list of fixed length strings
    validator.enforce(resource, students=['string(length=10)'])
    

This method can be used as a decorator factory for resources:

@validator.enforce(uid=int, friends=[int])
@app.route("/")
def res():
    return {}

Converter specifications given here are only checked when a field is present; see restrict() for specifying required fields.

Warning

Because of the way validators are hooked up, registering new specifications after the first request has run might cause unexpected behavior (and even internal server errors).

enforce_all(**validation_spec)[source]

Register a global validation specification.

This function works like enforce(), except that the validation specification is registered for all resources instead of a single one.

Global validation specifications have lower precedence than resource specific ones.

static regex(pattern, flags=0, template=None)[source]

Create a function that validates strings against a regular expression.

>>> func = Validator.regex("boy")
>>> func("boy")
'boy'
>>> func("That boy")
Traceback (most recent call last):
  ...
ValueError: That boy
>>> func("boy, that's handy.")
Traceback (most recent call last):
  ...
ValueError: boy, that's handy.

If you supply a template, it is used to construct a return value by doing backslash substitution:

>>> func = Validator.regex("(male|female)", template=r"Gender: \1")
>>> func("male")
'Gender: male'
>>> func("alien")
Traceback (most recent call last):
  ...
ValueError: alien
restrict([field, [field, [..., ]]]strip_extra=True)[source]

Restrict the input data to the given fields

Parameters:
  • field – A field name that should be allowed. An asterisk at the start of the field name indicates a required field (asterisks at the start of field names can be escaped with another asterisk character). This parameter can be used multiple times to indicate different fields.
  • strip_extra – Controls the behavior upon encountering a field not contained in the list, during validation. If True, the field will be removed. Otherwise, a UnexpectedFields is raised.

Once this method is called, any field names that do not appear in the list are disallowed.

validate(data)[source]

Validate the data with the validation specifications that have been collected.

This function must be called within an active request context in order to work.

Parameters:data (mapping, or object with gettable/settable fields) – Input data
Raises:ValidationFailed if one or more fields could not be validated.

This is an internal method.