Classes to perform filtering of objects based on their data members.
Given a list of objects and a textual filter expression, these classes allow you to determine which objects match the filter. The system has two main pieces: A parser for the supported grammar and a filter implementation.
Given any complying user-supplied grammar, it is parsed with a custom lexer based on GRR’s lexer and then compiled into an actual implementation by using the filter implementation. A filter implementation simply provides actual implementations for the primitives required to perform filtering. The compiled result is always a class supporting the Filter interface.
If we define a class called Car such as:
class Car(object): def __init__(self, code, color="white", doors=3): self.code = code self.color = color self.doors = 3
And we have two instances:
ford_ka = Car("FORDKA1", color="grey") toyota_corolla = Car("COROLLA1", color="white", doors=5) fleet = [ford_ka, toyota_corolla]
We want to find cars that are grey and have 3 or more doors. We could filter our fleet like this:
criteria = "(color is grey) and (doors >= 3)" parser = ContextFilterParser(criteria).Parse() compiled_filter = parser.Compile(LowercaseAttributeFilterImp) for car in fleet: if compiled_filter.Matches(car): print "Car %s matches the supplied filter." % car.code
The filter expression contains two subexpressions joined by an AND operator:
"color is grey" and "doors >= 3"
This means we want to search for objects matching these two subexpressions.
Let’s analyze the first one in depth “color is grey”:
- “color”: the left operand specifies a search path to look for the data. This tells our filtering system to look for the color property on passed objects.
- “is”: the operator. Values retrieved for the “color” property will be checked against the right operand to see if they are equal.
- “grey”: the right operand. It specifies an explicit value to check for.
So each time an object is passed through the filter, it will expand the value of the color data member, and compare its value against “grey”.
Because data members of objects are often not simple datatypes but other objects, the system allows you to reference data members within other data members by separating each by a dot. Let’s see an example:
Let’s add a more complex Car class with default tire data:
class CarWithTires(Car): def __init__(self, code, tires=None, color="white", doors=3): super(self, CarWithTires).__init__(code, color, doors) tires = tires or Tire("Pirelli", "PZERO") class Tire(object): def __init__(self, brand, code): self.brand = brand self.code = code
And two new instances:
ford_ka = CarWithTires("FORDKA", color="grey", tires=Tire("AVON", "ZT5")) toyota_corolla = Car("COROLLA1", color="white", doors=5) fleet = [ford_ka, toyota_corolla]
To filter a car based on the tire brand, we would use a search path of “tires.brand”.
Because the filter implementation provides the actual classes that perform handling of the search paths, operators, etc. customizing the behavior of the filter is easy. Three basic filter implementations are given:
- BaseFilterImplementation: search path expansion is done on attribute names as provided (case-sensitive).
- LowercaseAttributeFilterImp: search path expansion is done on the lowercased attribute name, so that it only accesses attributes, not methods.
- DictFilterImplementation: search path expansion is done on dictionary access to the given object. So “a.b” expands the object obj to obj[“a”][“b”]