microfilters

Minimalist skeleton classes to build filters in Python.


License
BSD-3-Clause
Install
pip install microfilters==0.1

Documentation

https://travis-ci.org/lukasbuenger/microfilters.png?branch=master

Microfilters

Microfilters introduces two minimalist skeleton classes to build filters and filter sets in Python. I use them mainly to implement filtering QuerySet objects in Django, but they can be safely used wherever the pattern fits.

Act of purpose

This was born out of me not quite feeling any of the currently available Django filtering apps, like django-filter or django-easyfilters. Don't get me wrong, both apps are well crafted and useful in almost any case where generic and/or introspective QuerySet filtering is the feature you're looking for. However, whenever I wanted to make customisations that go a little beyond the usual get_choices override or lookup_type adjustment, at some point I always felt like imposing my requirements onto something, that is not ment to provide that kind of flexibility.

What's shipping?

The suggestion of an approach. That's it. No magic out-of-the-box s**t involved here.

Requirements

  • Python 2.6, 2.7, 3.2, 3.3
  • six >= 1.3

Installation

With pip:

pip install microfilters

With setuptools:

git clone https://github.com/lukasbuenger/microfilters.git
cd microfilters/
python setup.py

Filter and FilterSet API

Microfilters literally consists of two classes that do actually nothing but providing a simple interface that IMHO covers most filtering purposes. Both classes only implement two methods (FilterSet being a subclass of Filter):

sanitize
Gets passed a value. Should raise an error if value is not valid. Should return a evaluated and / or modified version of value that will be used for filtering.
filter
The actual filter process. Accepts a item_list and a value (preferably sanitized) and should return a filtered version of item_list according to value.

The main difference between Filter and FilterSet is, that Filter objects should indeed filter lists, while FilterSet objects represent a set of Filter objects and should delegate the filter operation to the correspondent Filter if present. This leads to the following:

  • FilterSet.sanitize should accept and validate / modify a set of filter values instead of a single value.
  • FilterSet.filter should delegate the actual filtering of a given item_list to registered Filter objects based on the given value set.

As mentioned before, a FilterSet represents a collection of Filter objects. We register Filter objects to a FilterSet object by passing a dictionary to the constructor of the FilterSet. It may look something like this:

filter_set = FilterSet({
    'name': MyNameFilter(),
    'city': MyCityFilter()
})

Now if we pass a dictionary with correspondent keys to this FilterSet, it delegates the handling of each value to the registered Filter that got registered with the correspondent key:

filter_set.filter({
    'name': 'Lukas',
    'city': 'Zurich'
})

Examples

Both Filter and FilterSet literally cry for getting subclassed. Here are two simple implementation examples:

Filtering a list of strings:

class StringListFilter(Filter):

    def sanitize(self, value):
        if not isinstance(value, six.text_type):
            raise Exception
        return value

    def filter(self, item_list, value):
        return list(filter(lambda x: value in x, item_list))


filter_set = FilterSet({
    'param1': StringListFilter(),
    'param2': StringListFilter()
})

item_list = ['Lukas', 'Peter' , 'Barbara', 'Stuart']

print filter_set.filter(item_list, {
    'param1': 'u'
})
# ['Lukas', 'Stuart']

print filter_set.filter(item_list, {
    'param1': 'ar',
    'param2': 'u'
})
# ['Stuart']

Filtering of Django QuerySet objects, based on fields:

class CharFilter(Filter):

    def __init__(self, field_name):
        self.field_name = field_name

    def sanitize(self, value):
        if not isinstance(value, six.text_type):
            raise Exception
        return value

    def filter(self, item_list, value):
        return item_list.filter(Q(**{'%s__contains' % self.field_name: value})

filter_set = FilterSet({
    'name': CharFilter('username'),
    'email': CharFilter('email')
})

queryset = User.objects.all()

print filter_set.filter(queryset, {
    'name': 'lukasbuenger',
    'email': 'lukasbuenger@gmail.com'
})

Running the tests

To run the unit tests, simply execute the runtests.py in the microfilter/runtests subdirectory in an environment that has six installed:

python microfilter/runtests/runtests.py