Skip to content

Commit e4e70f0

Browse files
eds-collaboramgalka
authored andcommitted
Allow filters of the same type to be combined
When filters are applied, we require all specified filters to approve an item in order for it to match. When configuration is combined from multiple sources, this can make it extremely difficult to integrate filters into an existing list. We would like to extend an existing passlist, blocklist, or combination, but that's an impractical transformation on general YAML (the filters are lists, and the only sane transformation at the generic level is to concatenate lists). Specifying two passlists, blocklists or combinations does not have the desired effect, because of the `all` logic governing multiple filters, such that we get the intersection of the specifications, not the union. That is, e.g. for two passlists, only items on both lists can match. This commit adds a `combine` function to filters, such that they can opt to absorb multiple declarations of the same type. When a second passlist is encountered in the same context, the items in the second specification are passed to the object made for the first. It can then treat the specification as extending its own passlist. The FilterFactory has been updated to use this combine logic. Signed-off-by: Ed Smith <[email protected]>
1 parent 2299247 commit e4e70f0

File tree

1 file changed

+53
-2
lines changed

1 file changed

+53
-2
lines changed

kernelci/config/base.py

Lines changed: 53 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,31 @@ def match(self, **kw):
8080
"""Return True if the given *kw* keywords match the filter."""
8181
raise NotImplementedError("Filter.match() is not implemented")
8282

83+
def combine(self, items):
84+
"""Try to avoid making a new filter if we can make a combined
85+
filter matching both our existing data and the new items.
86+
87+
The *items* can be any data used to filter configurations.
88+
Return True if we can combine, False otherwise.
89+
"""
90+
return False
91+
92+
93+
def _merge_filter_lists(old, update):
94+
"""Merge the items for a Blocklist or Passlist.
95+
96+
*old* is the items from the existing filter list that we
97+
have already created.
98+
99+
*update* are the items of a new filter list, of the same type as
100+
*old*, loaded from another configuration source. We are
101+
going represent this second filter list declaration by
102+
updating *old*, rather than by having a new object to
103+
represent it.
104+
"""
105+
for key, value in update.items():
106+
old.setdefault(key, list()).extend(value)
107+
83108

84109
class Blocklist(Filter):
85110
"""Blocklist filter to discard certain configurations.
@@ -99,6 +124,10 @@ def match(self, **kw):
99124

100125
return True
101126

127+
def combine(self, items):
128+
_merge_filter_lists(self._items, items)
129+
return True
130+
102131

103132
class Passlist(Filter):
104133
"""Passlist filter to only accept certain configurations.
@@ -118,6 +147,10 @@ def match(self, **kw):
118147

119148
return True
120149

150+
def combine(self, items):
151+
_merge_filter_lists(self._items, items)
152+
return True
153+
121154

122155
class Regex(Filter):
123156
"""Regex filter to only accept certain configurations.
@@ -156,6 +189,14 @@ def match(self, **kw):
156189
filter_values = tuple(kw.get(k) for k in self._keys)
157190
return filter_values in self._values
158191

192+
def combine(self, items):
193+
keys = tuple(items['keys'])
194+
if keys != self._keys:
195+
return False
196+
197+
self._values.extend([tuple(values) for values in items['values']])
198+
return True
199+
159200

160201
class FilterFactory(YAMLObject):
161202
"""Factory to create filters from YAML data."""
@@ -171,10 +212,20 @@ class FilterFactory(YAMLObject):
171212
def from_yaml(cls, filter_params):
172213
"""Iterate through the YAML filters and return Filter objects."""
173214
filter_list = []
215+
filters = {}
216+
174217
for f in filter_params:
175218
for filter_type, items in f.items():
176-
filter_cls = cls._classes[filter_type]
177-
filter_list.append(filter_cls(items))
219+
for g in filters.get(filter_type, []):
220+
if g.combine(items):
221+
break
222+
else:
223+
filter_cls = cls._classes[filter_type]
224+
filter_instance = filter_cls(items)
225+
filters.setdefault(filter_type, list()).append(
226+
filter_instance)
227+
filter_list.append(filter_instance)
228+
178229
return filter_list
179230

180231
@classmethod

0 commit comments

Comments
 (0)