Sequence filtering¶
The momotor.bundles.utils.filters package provides two filterable sequences, the
FilterableList and
FilterableTuple.
Both add the same functions to the standard python list and tuple types.
The interface is loosely based on Django QuerySets field lookups.
The examples in this documentation use the following filterable lists:
>>> data_str = FilterableList([ItemA(a='abc'), ItemA(a='ABC'), ItemA(a='def'), ItemA(a='abc/def')])
>>> data_int = FilterableList([ItemA(a=1), ItemA(a=2), ItemA(a=3)])
>>> data_set = FilterableList([ItemA(a={1, 2}), ItemA(a={2, 3}), ItemA(a={3, 4})])
>>> data_ab = FilterableList([ItemAB(a=1, b=1), ItemAB(a=1, b=2), ItemAB(a=2, b=2), ItemAB(a=3, b=3)])
Where ItemA and ItemAB are datatypes having both an a attribute, and ItemAB also has a b attribute.
Filtering and excluding¶
The most basic lookup looks like this:
>>> data_ab.filter(a=1) # filter all items with a==1
[ItemAB(a=1, b=1), ItemAB(a=1, b=2)]
This filters data_ab for any objects containing an a attribute with value equal to 1.
The result of filter() is itself another filterable type,
so filters can be chained:
>>> data_ab.filter(a=1).filter(b=2) # filter all items with a==1, the filter the result for a==2
[ItemAB(a=1, b=2)]
FilterableList.exclude(...) excludes items:
>>> data_ab.exclude(a=1) # exclude all items where a==1
[ItemAB(a=2, b=2), ItemAB(a=3, b=3)]
Providing multiple lookups in filter() or exclude() will filter or exclude objects matching all the lookups:
>>> data_ab.filter(a=1, b=2) # filter all items with a==1 and b==2
[ItemAB(a=1, b=2)]
>>> data_ab.exclude(a=1, b=2) # exclude all items with a==1 and b==2
[ItemAB(a=1, b=1), ItemAB(a=2, b=2), ItemAB(a=3, b=3)]
Not() object¶
Lookups can be negated using a Not object:
>>> data_ab.filter(Not(a=1)) # filter all items where `a` is not 1
[ItemAB(a=2, b=2), ItemAB(a=3, b=3)]
FilterableList.exclude(...) is a shortcut for FilterableList.filter(Not(...))
All() object¶
The All object can be used to make lookups with multiple arguments
more explicit:
>>> data_ab.filter(All(a=1, b=2)) # filter all items with a==1 and b==2
[ItemAB(a=1, b=2)]
Any() object¶
Lookups can also be combined using the Any object to find an object
which matches any of the requested lookups:
>>> data_ab.filter(Any(a=1, b=2)) # filter all items with a==1 OR b==2
[ItemAB(a=1, b=1), ItemAB(a=1, b=2), ItemAB(a=2, b=2)]
Combining filter objects¶
Lookup objects can be combined, for example:
>>> data_ab.filter(Any(All(a=1, b=2), b=3)) # Look for items with (a==1 AND b==2) OR b==3
[ItemAB(a=1, b=2), ItemAB(a=3, b=3)]
F() object¶
Using keyword arguments it is not possible to filter for multiple values of the same property, since
filter(a=1, a=3) has a repeated keyword a:
>>> data_ab.filter(a=1, a=3) # this is a SyntaxError
Traceback (most recent call last):
...
SyntaxError: keyword argument repeated
To look for data_ab values for the same attribute, use the F object
to wrap individual queries:
>>> data_ab.filter(F(a=1), F(a=3)) # filter all items with a==1 AND a==3 (which is nonsensical indeed, but this is just an example)
[]
This can also be combined with the other filter objects like Any
>>> data_ab.filter(Any(F(a=1), F(a=3))) # filter items with a==1 OR a==3
[ItemAB(a=1, b=1), ItemAB(a=1, b=2), ItemAB(a=3, b=3)]
F objects can also be used to easily pass on filter queries as
arguments to functions:
>>> def filter_data(f: F):
... return data_ab.filter(f)
>>> for f in [F(a=1), F(a=2)]:
... print(filter_data(f))
[ItemAB(a=1, b=1), ItemAB(a=1, b=2)]
[ItemAB(a=2, b=2)]
Any, All and
Not are subclasses of F.
When mixing F arguments and keyword arguments, Python syntax requires
the positional arguments to be provided before the keyword arguments. The following is invalid:
>>> data_ab.filter(a=1, Any(a=2, b=3)) # positional argument follows keyword argument
Traceback (most recent call last):
...
SyntaxError: positional argument follows keyword argument
Using an F object solves this issue:
>>> data_ab.filter(F(a=1), Any(a=2, b=3))
[]
Negation¶
The Any and All classes
support negation using the ~ operator, for example ~All(...) is the same as Not(All(...))
Lookup types¶
Additionally to filtering or excluding exact values, there are several other lookup types.
A lookup type is added to the lookup by separating the attribute name and the lookup with a double underscore
__, eg. a__ne applies the not-equal lookup to the a attribute.
The following filter lookup operations are available:
Lookup type |
Case sensitive lookup |
Case insensitive lookup |
|---|---|---|
Is-equal |
||
Not-equal |
||
Contains |
||
In |
||
Starts-with |
||
Ends-with |
||
Glob |
||
Recursive glob |
||
Regular expression |
Note
Case insensitive lookups only accept strings or a sequence of strings. Any other value
will raise a TypeError:
>>> data_int.filter(a__ine=1)
Traceback (most recent call last):
...
TypeError: Expected a string or sequence of strings, got <class 'int'>
Is-equal lookup¶
Filters exact values.
Operator: No operator, is or eq
Examples:
>>> data_str.filter(a='abc')
[ItemA(a='abc')]
>>> data_str.filter(a__is='ABC')
[ItemA(a='ABC')]
>>> data_str.filter(a__eq='def')
[ItemA(a='def')]
>>> data_int.filter(a=1)
[ItemA(a=1)]
>>> data_int.filter(a='abc')
[]
Is-equal lookup (case insensitive)¶
Filters string attributes case insensitive.
Lookup operator: ieq / iis
Examples:
>>> data_str.filter(a__ieq='abc')
[ItemA(a='abc'), ItemA(a='ABC')]
>>> data_int.filter(a__iis='abc')
[]
Not-equal lookup¶
Lookup operator: ne
Usage example:
>>> data_str.filter(a__ne='abc')
[ItemA(a='ABC'), ItemA(a='def'), ItemA(a='abc/def')]
>>> data_int.filter(a__ne=1)
[ItemA(a=2), ItemA(a=3)]
Not-equal lookup (case insensitive)¶
Lookup operator: ine
Usage example:
>>> data_str.filter(a__ine='abc')
[ItemA(a='def'), ItemA(a='abc/def')]
Contains lookup¶
Lookup operator: contains
Works on Python types implementing object.__contains__() like str, sequences and sets.
Usage example:
>>> data_str.filter(a__contains='a')
[ItemA(a='abc'), ItemA(a='abc/def')]
>>> data_set.filter(a__contains=2)
[ItemA(a={1, 2}), ItemA(a={2, 3})]
Contains lookup (case insensitive)¶
Lookup operator: icontains
Works on Python str and sets and sequences containing strings.
Usage example:
>>> data_str.filter(a__icontains='a')
[ItemA(a='abc'), ItemA(a='ABC'), ItemA(a='abc/def')]
In lookup¶
Lookup operator: in
The reverse of contains. The lookup value must implement object.__contains__()
Usage example:
>>> data_str.filter(a__in={'abc', 'def', 'ghi'})
[ItemA(a='abc'), ItemA(a='def')]
>>> data_int.filter(a__in={0, 1, 2})
[ItemA(a=1), ItemA(a=2)]
In lookup (case insensitive)¶
Lookup operator: iin
Usage example:
>>> data_str.filter(a__iin={'abc', 'def', 'ghi'})
[ItemA(a='abc'), ItemA(a='ABC'), ItemA(a='def')]
Starts-with lookup¶
Lookup operator: startswith
Usage example:
>>> data_str.filter(a__startswith='a')
[ItemA(a='abc'), ItemA(a='abc/def')]
Starts-with lookup (case insensitive)¶
Lookup operator: istartswith
Usage example:
>>> data_str.filter(a__istartswith='a')
[ItemA(a='abc'), ItemA(a='ABC'), ItemA(a='abc/def')]
Ends-with lookup¶
Lookup operator: endswith
Usage example:
>>> data_str.filter(a__endswith='c')
[ItemA(a='abc')]
Ends-with lookup (case insensitive)¶
Lookup operator: iendswith
Usage example:
>>> data_str.filter(a__iendswith='c')
[ItemA(a='abc'), ItemA(a='ABC')]
Glob lookup¶
Lookup operator: glob
Matches a string against a pattern. The pattern is a string with special characters:
*matches any number of characters?matches any single character[seq]matches any character in seq[!seq]matches any character not in seq
Unlike the rglob filter, path separators characters (/ and \) are not considered
special and are matched by the * pattern.
Usage example:
>>> data_str.filter(a__glob='a*')
[ItemA(a='abc'), ItemA(a='abc/def')]
Glob lookup (case insensitive)¶
Lookup operator: iglob
Matches a string against a pattern, case insensitive. The pattern is a string with special characters:
*matches any number of characters?matches any single character[seq]matches any character in seq[!seq]matches any character not in seq
Unlike the irglob filter, path separators characters (/ and \) are not considered
special and are matched by the * pattern.
Usage example:
>>> data_str.filter(a__iglob='a*')
[ItemA(a='abc'), ItemA(a='ABC'), ItemA(a='abc/def')]
Recursive glob lookup¶
Lookup operator: rglob
Matches a string against a recursive glob pattern. The pattern is a string with special characters:
*matches any number of characters, excluding path separators**matches any number of characters, including path separators (except a trailing path separator, see below)?matches any single character[seq]matches any character in seq[!seq]matches any character not in seq
To match a string ending in a path separator (/ or \, indicating a directory path), the pattern
must explicitly end in a path separator as well, i.e. ** alone will not match strings ending with a path
separator, and **/ or **\ will only match strings ending with a path separator.
Usage example:
>>> data_str.filter(a__rglob='a*')
[ItemA(a='abc')]
>>> data_str.filter(a__rglob='a**')
[ItemA(a='abc'), ItemA(a='abc/def')]
Recursive glob lookup (case insensitive)¶
Lookup operator: irglob
Matches a string against a recursive glob pattern, case insensitive. The pattern is a string with special characters:
*matches any number of characters, excluding path separators**matches any number of characters, including path separators (except a trailing path separator, see below)?matches any single character[seq]matches any character in seq[!seq]matches any character not in seq
To match a string ending in a path separator (/, indicating a directory path), the pattern
must explicitly end in a path separator as well, i.e. ** alone will not match strings ending with a path
separator, and **/ or **\ will only match strings ending with a path separator.
Usage example:
>>> data_str.filter(a__irglob='a*')
[ItemA(a='abc'), ItemA(a='ABC')]
>>> data_str.filter(a__irglob='a**')
[ItemA(a='abc'), ItemA(a='ABC'), ItemA(a='abc/def')]
Regular expression lookup¶
Lookup operator: re
Usage example:
>>> data_str.filter(a__re=r'^.b.$')
[ItemA(a='abc')]
Regular expression lookup (case insensitive)¶
Lookup operator: ire
Usage example:
>>> data_str.filter(a__ire=r'^.b.$')
[ItemA(a='abc'), ItemA(a='ABC')]
Additional methods¶
FilterableList and
FilterableTuple have several more functions:
API¶
- class momotor.bundles.utils.filters.All(*args, **kwargs)¶
“All” filter operator
All the lookups in the argument list will need to match for an item to match this filter, see the All object documentation for an example.
(alias of
F)
- class momotor.bundles.utils.filters.Any(*args, **kwargs)¶
Logical OR filter operator.
Any of the filter conditions can match for an item to match this filter. Uses OR logic instead of the default AND logic of
F.- Parameters:
args (
_Filter) – Filter objects to combine with OR logickwargs – Field lookups to combine with OR logic
- Examples:
>>> from types import SimpleNamespace >>> obj1 = SimpleNamespace(name='test', value=1) >>> obj2 = SimpleNamespace(name='other', value=42)
>>> # Match either condition: >>> f = Any(name='test', value=42) >>> f(obj1) # Matches name='test' True >>> f(obj2) # Matches value=42 True
- class momotor.bundles.utils.filters.F(*args, **kwargs)¶
Filter operator for building complex queries.
The base filter operator that can be used to create filtering expressions using field lookup syntax. Supports combining multiple filter conditions with AND logic. Can be used to prevent multiple keywords SyntaxErrors in Python.
- Parameters:
args (
_Filter) – Additional filter objects to combinekwargs – Field lookups in the format
field__operator=value
- Examples:
>>> from types import SimpleNamespace >>> obj = SimpleNamespace(name='test', value=42)
>>> # Basic equality (same as field='value'): >>> f = F(name='test') >>> f(obj) True
>>> # Using operators: >>> f = F(name__startswith='te') >>> f(obj) True
>>> # Combining multiple conditions (AND logic): >>> f = F(name='test', value=42) >>> f(obj) True
See the filtering documentation for available operators and usage patterns.
- class momotor.bundles.utils.filters.FilterableList(iterable=(), /)¶
A
listwith additional functions to filter the objects in the listSee Sequence filtering section in the documentation
- append(object, /)¶
Append object to the end of the list.
- clear()¶
Remove all items from list.
- copy()¶
Return a shallow copy of the list.
- count(value, /)¶
Return number of occurrences of value.
- exclude(*args, **kwargs)¶
Return filtered collection excluding matching elements.
- Parameters:
args – Filter objects to exclude
kwargs – Field lookups in format
field__operator=value
- Return type:
Self- Returns:
New instance of same type with non-matching elements
- extend(iterable, /)¶
Extend list by appending elements from the iterable.
- filter(*args, **kwargs)¶
Return filtered collection containing only matching elements.
- Parameters:
args – Filter objects to apply
kwargs – Field lookups in format
field__operator=value
- Return type:
Self- Returns:
New instance of same type with filtered elements
- filter_with(func)¶
Filter elements using a callable function.
- index(value, start=0, stop=9223372036854775807, /)¶
Return first index of value.
Raises ValueError if the value is not present.
- insert(index, object, /)¶
Insert object before index.
- pop(index=-1, /)¶
Remove and return item at index (default last).
Raises IndexError if list is empty or index is out of range.
- remove(value, /)¶
Remove first occurrence of value.
Raises ValueError if the value is not present.
- reverse()¶
Reverse IN PLACE.
- sort(*, key=None, reverse=False)¶
Sort the list in ascending order and return None.
The sort is in-place (i.e. the list itself is modified) and stable (i.e. the order of two equal elements is maintained).
If a key function is given, apply it once to each list item and sort them, ascending or descending, according to their function values.
The reverse flag can be set to sort in descending order.
- class momotor.bundles.utils.filters.FilterableTuple(iterable=())¶
A
tuplewith additional functions to filter the objects in the tupleSee Sequence filtering section in the documentation
- count(value, /)¶
Return number of occurrences of value.
- exclude(*args, **kwargs)¶
Return filtered collection excluding matching elements.
- Parameters:
args – Filter objects to exclude
kwargs – Field lookups in format
field__operator=value
- Return type:
Self- Returns:
New instance of same type with non-matching elements
- filter(*args, **kwargs)¶
Return filtered collection containing only matching elements.
- Parameters:
args – Filter objects to apply
kwargs – Field lookups in format
field__operator=value
- Return type:
Self- Returns:
New instance of same type with filtered elements
- filter_with(func)¶
Filter elements using a callable function.
- index(value, start=0, stop=9223372036854775807, /)¶
Return first index of value.
Raises ValueError if the value is not present.
- class momotor.bundles.utils.filters.Not(*args, **kwargs)¶
Negation filter operator.
Inverts the logic of the contained filter(s). Any item that would normally match the filter conditions will be excluded, and vice versa.
- Parameters:
args (
_Filter) – Filter objects to negatekwargs – Field lookups to negate
- Examples:
>>> from types import SimpleNamespace >>> obj = SimpleNamespace(name='test')
>>> # Negate a simple filter: >>> f = Not(name='test') >>> f(obj) False
>>> f = Not(name='other') >>> f(obj) True