Writing Your Own Filters
Although the Filters library comes with lots of built-in filters, oftentimes it is useful to be able to write your own.
To create a new filter, write a class that extends
filters.BaseFilter
and implement the _apply
method:
import filters as f
class Pkcs7Pad(f.BaseFilter):
block_size = 16
def _apply(self, value):
extra_bytes = self.block_size - (len(value) % self.block_size)
return value + bytes([extra_bytes] * extra_bytes)
Validation
To implement validation in your filter, add the following:
Define a unique code for each validation error.
Define an error message template for each validation error.
Add the logic to the filter’s
_apply
method.
Here’s the Pkcs7Pad
filter with a little bit of validation logic:
import filters as f
class Pkcs7Pad(f.BaseFilter):
CODE_INVALID_TYPE = 'invalid_type'
templates = {
CODE_INVALID_TYPE = 'Binary string required.',
}
block_size = 16
def _apply(self, value):
if not isinstance(value, bytes):
return self._invalid_value(value, self.CODE_INVALID_TYPE)
extra_bytes = self.block_size - (len(value) % self.block_size)
return value + bytes([extra_bytes] * extra_bytes)
Invoking Other Filters
You can also invoke other filters in your custom filters by calling the
self._filter
method.
For example, we can simplify the implementation of Pkcs7Pad
by incorporating
the filters.ByteString
filter:
import filters as f
class Pkcs7Pad(f.BaseFilter):
block_size = 16
def _apply(self, value):
# The incoming value must be a byte string.
value = self._filter(value, f.Type(bytes))
if self._has_errors:
return None
extra_bytes = self.block_size - (len(value) % self.block_size)
return value + bytes([extra_bytes] * extra_bytes)
Important
self._filter
will not raise an exception if the value is invalid; your
filter must check self._has_errors
after calling self._filter(...)
!
Unit Tests
To help you unit test your custom filters, the Filters library provides a helper
class called filters.test.BaseFilterTestCase
.
This class defines two methods that you can use to test your filter:
assertFilterPasses
: Given an input value, asserts that the filter returns an expected value when applied.assertFilterErrors
: Given an input value, asserts that the filter generates the expected filter error messages when applied.
Here’s a starter test case for Pkcs7Pad
:
import filters as f
from filters.test import BaseFilterTestCase
class Pkcs7PadTestCase(BaseFilterTestCase):
# Specify your filter as ``filter_type``.
filter_type = Pkcs7Pad
def test_pass_none(self):
"""``None`` always passes this filter."""
self.assertFilterPasses(None)
def test_pass_padding(self):
"""Padding a value to the correct length."""
# Use ``self.assertFilterPasses`` to check the result of filtering a
# valid value.
self.assertFilterPasses(
# If this is the input...
b'Hello, world!',
# ... this is the expected result.
b'Hello, world!\x03\x03\x03'
)
def test_fail_wrong_type(self):
"""The incoming value is not a byte string."""
# Use ``self.assertFilterErrors`` to check the errors from filtering
# an invalid value.
self.assertFilterErrors(
# If this is the input...
'Hello, world!',
# ... these are the expected filter errors.
[f.Type.CODE_WRONG_TYPE],
)
Registering Your Filters (Optional)
Once you’ve packaged up your filters, you can register them with the Extensions
framework to add them to the (nearly) top-level filters.ext
namespace.
This is an optional step; it may make your filters easier to use, though there are some trade-offs.
See Extending the Filters Namespace for more information.