Getting Started

The fastest way to get started with filters is to use the FilterRunner class. This class provides an interface very similar to a Django form.

import datetime
import filters as f

# Incoming data.
data = u'1879-03-14'

# Initialize the FilterRunner.
runner = f.FilterRunner(f.Date, data)

if runner.is_valid():
  # Input is valid; do something with the filtered data.
  cleaned_data = runner.cleaned_data
  assert cleaned_data == datetime.date(1879, 3, 14)

else:
  # Input is not valid; display error message(s) for each incoming value.
  for key, errors in runner.errors.items():
    print('{key}:'.format(key=key))
    for error in errors:
      print('  - ({error[code]}) {error[message]}'.format(error=error))

FilterRunner provides a few key attributes to make it easy to apply filters:

  • is_valid(): Returns whether the value is valid.

  • cleaned_data: If the value is valid, this property holds the filtered value(s).

  • errors: If the value is not valid, this property holds the validation errors.

Reusing FilterRunners

If you want to run the same set of filters on different values, you can create a single FilterRunner instance and call its apply() method:

import filters as f

runner = f.FilterRunner(f.Choice({'foo', 'bar', 'baz', 'luhrmann'}))

input_1 = 'foo'
input_2 = 'foobie'

runner.apply(input_1)
assert runner.is_valid() is True
assert runner.cleaned_data == 'foo'

runner.apply(input_2)
assert runner.is_valid() is False
assert runner.cleaned_data is None

Chaining Filters

The filters library conforms to the unix philosophy of, “Do One Thing, and Do It Well”.

Each filter provides a specific transformation and/or validation feature. This alone can be useful, but the real power of the filters library lies in its ability to “chain” filters together.

By using the | operator, you can “pipe” the output of one filter directly into the input of another. This allows you to quickly and easily create complex data pipelines.

Here’s an example:

import filters as f

# Convert to unicode, strip leading and trailing whitespace, reject empty
# string, fold case and split into words.
filter_ = f.Unicode | f.Strip | f.NotEmpty | f.CaseFold | f.Split(r'\W+')

runner = f.FilterRunner(filter_, '   Остерегайтесь Дуга   ')
assert runner.is_valid() is True
assert runner.cleaned_data == ['остерегайтесь', 'дуга']

runner = f.FilterRunner(filter_, '\r\n')
assert runner.is_valid() is False

Much Ado About None

None is a special value to the Filters library. By default, it passes every filter, no matter how strictly configured.

For example:

import filters as f

# Convert to unicode, strip leading and trailing whitespace, reject empty
# string, fold case and split into words.
filter_ = f.Unicode | f.Strip | f.NotEmpty | f.CaseFold | f.Split(r'\W+')

runner = f.FilterRunner(filter_, None)
assert runner.is_valid() is True
assert runner.cleaned_data is None

If you want to reject None, add the Required filter to your chain:

import filters as f

# Note that we replace ``NotEmpty`` with ``Required``.
filter_ = f.Unicode | f.Strip | f.Required | f.CaseFold | f.Split(r'\W+')

runner = f.FilterRunner(filter_, None)

assert runner.is_valid() is False

Next Steps

See Simple Filters for a list of all the filters that come bundled with the Filters library.

Be sure to pay special attention to Complex Filters, which lists filters designed exclusively to work with other filters, allowing you to construct powerful data schemas and transformation pipelines.

There are also several Official Extensions that you can install, to add even more filters to work with.

Once you’ve gotten the hang of working with filters, you’ll want to write your own filters and macros, so that you can reduce code duplication and inject your own functionality into filter pipelines.