Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion octue/mixins/filterable.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,12 @@ class Filterable:
def satisfies(self, filter_name, filter_value):
""" Check that the instance satisfies the given filter for the given filter value. """
attribute_name, filter_action = self._split_filter_name(filter_name)
attribute = getattr(self, attribute_name)

try:
attribute = getattr(self, attribute_name)
except AttributeError:
raise exceptions.InvalidInputException(f"An attribute named {attribute_name!r} does not exist on {self!r}.")

filter_ = self._get_filter(attribute, filter_action)
return filter_(attribute, filter_value)

Expand Down
19 changes: 18 additions & 1 deletion octue/resources/filter_containers.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
from octue import exceptions


def _filter(instance, filter_name=None, filter_value=None):
""" Returns a new FilterSet containing only the Filterables to which the given filter criteria apply.
""" Returns a new instance containing only the Filterables to which the given filter criteria apply.

Say we want to filter by files whose extension equals "csv". We want to be able to do...

Expand All @@ -19,9 +22,23 @@ def _filter(instance, filter_name=None, filter_value=None):
return instance.__class__((item for item in instance if item.satisfies(filter_name, filter_value)))


def _order_by(instance, attribute_name, reverse=False):
""" Order the instance by the given attribute_name, returning the instance's elements as a new FilterList (not a
FilterSet.
"""
try:
return FilterList(sorted(instance, key=lambda item: getattr(item, attribute_name), reverse=reverse))
except AttributeError:
raise exceptions.InvalidInputException(
f"An attribute named {attribute_name!r} does not exist on one or more members of {instance!r}."
)


class FilterSet(set):
filter = _filter
order_by = _order_by


class FilterList(list):
filter = _filter
order_by = _order_by
5 changes: 5 additions & 0 deletions tests/mixins/test_filterable.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,11 @@ def test_error_raised_when_invalid_filter_name_received(self):
with self.assertRaises(exceptions.InvalidInputException):
FilterableSubclass().satisfies(filter_name="invalid_filter_name", filter_value=None)

def test_error_raised_when_non_existent_attribute_name_received(self):
""" Ensure an error is raised when a non-existent attribute name is used in the filter name. """
with self.assertRaises(exceptions.InvalidInputException):
FilterableSubclass().satisfies(filter_name="boogaloo__is_a_dance", filter_value=True)

def test_error_raised_when_valid_but_non_existent_filter_name_received(self):
""" Ensure an error is raised when a valid but non-existent filter name is received. """
with self.assertRaises(exceptions.InvalidInputException):
Expand Down
44 changes: 44 additions & 0 deletions tests/resources/test_filter_containers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
from tests.base import BaseTestCase

from octue import exceptions
from octue.mixins import Filterable
from octue.resources.filter_containers import FilterList, FilterSet


class Cat(Filterable):
def __init__(self, name=None, previous_names=None, age=None):
self.name = name
self.previous_names = previous_names
self.age = age


class TestFilterSet(BaseTestCase):
def test_ordering_by_a_non_existent_attribute(self):
""" Ensure an error is raised if ordering is attempted by a non-existent attribute. """
filter_set = FilterSet([Cat(age=5), Cat(age=4), Cat(age=3)])
with self.assertRaises(exceptions.InvalidInputException):
filter_set.order_by("dog-likeness")

def test_order_by_with_string_attribute(self):
""" Test ordering a FilterSet by a string attribute returns an appropriately ordered FilterList. """
cats = [Cat(name="Zorg"), Cat(name="James"), Cat(name="Princess Carolyn")]
sorted_filter_set = FilterSet(cats).order_by("name")
self.assertEqual(sorted_filter_set, FilterList([cats[1], cats[2], cats[0]]))

def test_order_by_with_int_attribute(self):
""" Test ordering a FilterSet by an integer attribute returns an appropriately ordered FilterList. """
cats = [Cat(age=5), Cat(age=4), Cat(age=3)]
sorted_filter_set = FilterSet(cats).order_by("age")
self.assertEqual(sorted_filter_set, FilterList(reversed(cats)))

def test_order_by_list_attribute(self):
""" Test that ordering by list attributes orders by the size of the list. """
cats = [Cat(previous_names=["Scatta", "Catta"]), Cat(previous_names=["Kitty"]), Cat(previous_names=[])]
sorted_filter_set = FilterSet(cats).order_by("previous_names")
self.assertEqual(sorted_filter_set, FilterList(reversed(cats)))

def test_order_by_in_reverse(self):
""" Test ordering in reverse works correctly. """
cats = [Cat(age=5), Cat(age=3), Cat(age=4)]
sorted_filter_set = FilterSet(cats).order_by("age", reverse=True)
self.assertEqual(sorted_filter_set, FilterList([cats[0], cats[2], cats[1]]))