diff --git a/neutron_lib/db/model_query.py b/neutron_lib/db/model_query.py index 2cf33a0ab..18c74bc1b 100644 --- a/neutron_lib/db/model_query.py +++ b/neutron_lib/db/model_query.py @@ -174,16 +174,8 @@ def apply_filters(query, model, filters, context=None): # do multiple equals matches query = query.filter( or_(*[column == v for v in value])) - elif isinstance(value, obj_utils.StringMatchingFilterObj): - if value.is_contains: - query = query.filter( - column.contains(value.contains)) - elif value.is_starts: - query = query.filter( - column.startswith(value.starts)) - elif value.is_ends: - query = query.filter( - column.endswith(value.ends)) + elif isinstance(value, obj_utils.FilterObj): + query = query.filter(value.filter(column)) elif None in value: # in_() operator does not support NULL element so we have # to do multiple equals matches diff --git a/neutron_lib/objects/utils.py b/neutron_lib/objects/utils.py index faafa3eb6..11c4599e5 100644 --- a/neutron_lib/objects/utils.py +++ b/neutron_lib/objects/utils.py @@ -10,8 +10,11 @@ # License for the specific language governing permissions and limitations # under the License. +import abc import copy +import six + from neutron_lib import exceptions @@ -25,7 +28,15 @@ def convert_filters(**kwargs): return result -class StringMatchingFilterObj(object): +@six.add_metaclass(abc.ABCMeta) +class FilterObj(object): + + @abc.abstractmethod + def filter(self, column): + pass + + +class StringMatchingFilterObj(FilterObj): @property def is_contains(self): return bool(getattr(self, "contains", False)) @@ -45,6 +56,9 @@ class StringContains(StringMatchingFilterObj): super(StringContains, self).__init__() self.contains = matching_string + def filter(self, column): + return column.contains(self.contains) + class StringStarts(StringMatchingFilterObj): @@ -52,9 +66,35 @@ class StringStarts(StringMatchingFilterObj): super(StringStarts, self).__init__() self.starts = matching_string + def filter(self, column): + return column.startswith(self.starts) + class StringEnds(StringMatchingFilterObj): def __init__(self, matching_string): super(StringEnds, self).__init__() self.ends = matching_string + + def filter(self, column): + return column.endswith(self.ends) + + +class NotIn(FilterObj): + + def __init__(self, value): + super(NotIn, self).__init__() + self.value = value + + def filter(self, column): + return ~column.in_(self.value) + + +class NotEqual(FilterObj): + + def __init__(self, value): + super(NotEqual, self).__init__() + self.value = value + + def filter(self, column): + return column != self.value diff --git a/neutron_lib/tests/unit/objects/__init__.py b/neutron_lib/tests/unit/objects/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/neutron_lib/tests/unit/objects/test_utils.py b/neutron_lib/tests/unit/objects/test_utils.py new file mode 100644 index 000000000..7464dd1bd --- /dev/null +++ b/neutron_lib/tests/unit/objects/test_utils.py @@ -0,0 +1,59 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from neutron_lib.objects import utils as obj_utils +from neutron_lib.tests import _base as base + + +class TestUtils(base.BaseTestCase): + + def test_get_objects_with_filters_not_in(self): + + class FakeColumn(object): + def __init__(self, column): + self.column = column + + def in_(self, value): + self.value = value + return self + + def __invert__(self): + return list(set(self.column) - set(self.value)) + + filter_obj = obj_utils.NotIn([1, 2, 3]) + fake_column = FakeColumn([1, 2, 4, 5]) + self.assertEqual([4, 5], filter_obj.filter(fake_column)) + + fake_column = FakeColumn([1, 2]) + self.assertEqual([], filter_obj.filter(fake_column)) + + fake_column = FakeColumn([4, 5]) + self.assertEqual([4, 5], filter_obj.filter(fake_column)) + + def test_get_objects_with_filters_not_equal(self): + + class FakeColumn(object): + def __init__(self, column): + self.column = column + + def __ne__(self, value): + return [item for item in self.column if item != value] + + filter_obj = obj_utils.NotEqual(1) + fake_column = FakeColumn([1, 2, 4, 5]) + self.assertEqual([2, 4, 5], filter_obj.filter(fake_column)) + + fake_column = FakeColumn([1]) + self.assertEqual([], filter_obj.filter(fake_column)) + + fake_column = FakeColumn([4, 5]) + self.assertEqual([4, 5], filter_obj.filter(fake_column)) diff --git a/releasenotes/notes/support-custom-filter-f4a15bb5b38b7d3e.yaml b/releasenotes/notes/support-custom-filter-f4a15bb5b38b7d3e.yaml new file mode 100644 index 000000000..743ffbd22 --- /dev/null +++ b/releasenotes/notes/support-custom-filter-f4a15bb5b38b7d3e.yaml @@ -0,0 +1,9 @@ +--- +prelude: > + This release adds support for custom filtering in versioned object. +features: + - | + A class called ``FilterObj`` is introduced. + This is the base class from which the custom filter class should inherit. + This release also implements two filter class: ``NotIn`` and ``NotEqual``. + The class ``StringMatchingFilterObj`` is now a subclass of ``FilterObj``.