diff --git a/pydictdisplayfilter/__init__.py b/pydictdisplayfilter/__init__.py
index ac26bd0..e2dceae 100644
--- a/pydictdisplayfilter/__init__.py
+++ b/pydictdisplayfilter/__init__.py
@@ -14,4 +14,5 @@
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
-from pydictdisplayfilter.display_filters import DictDisplayFilter, ListDisplayFilter
\ No newline at end of file
+from pydictdisplayfilter.display_filters import \
+ DictDisplayFilter, ListDisplayFilter, ObjectDisplayFilter, SQLDisplayFilter
diff --git a/pydictdisplayfilter/display_filters.py b/pydictdisplayfilter/display_filters.py
index d852c53..c4a59a2 100644
--- a/pydictdisplayfilter/display_filters.py
+++ b/pydictdisplayfilter/display_filters.py
@@ -122,7 +122,7 @@ def filter(self, display_filter: str):
class DictDisplayFilter(BaseDisplayFilter):
- """ Allows to filter a dictionary using a display filter. """
+ """ Allows to filter a list of dictionaries using a display filter. """
def __init__(self,
data: List[dict],
@@ -138,7 +138,7 @@ def __init__(self,
self._data = data
def filter(self, display_filter: str):
- """ Filters the data using the display filter. """
+ """ Filters the dictionaries using the display filter. """
expressions = self._display_filter_parser.parse(display_filter)
yield from self._filter_data(self._data, expressions)
@@ -246,3 +246,33 @@ def filter(self, display_filter: str):
expressions = self._display_filter_parser.parse(display_filter)
table_data = self._get_table_data()
yield from self._filter_data(table_data, expressions)
+
+
+class ObjectDisplayFilter(BaseDisplayFilter):
+ """ Allows to filter a list of objects using a display filter. """
+
+ def __init__(self,
+ data: List[object],
+ field_names: List[str] = None,
+ functions: Dict[str, Callable] = None,
+ slicers: List[BasicSlicer] = None,
+ evaluator: Evaluator = None):
+ """
+ Initializes the DictDisplayFilter.
+ :param data: A list of dictionaries to filter on.
+ """
+ super().__init__(field_names=field_names, functions=functions, slicers=slicers, evaluator=evaluator)
+ self._data = data
+
+ def _filter_data(self, data: List[object], expressions: List[Union[Expression, str]]) -> List:
+ if expressions:
+ for item in data:
+ if self._evaluate_expressions(expressions, item.__dict__):
+ yield item
+ else:
+ yield from data
+
+ def filter(self, display_filter: str):
+ """ Filters the objects using the display filter. """
+ expressions = self._display_filter_parser.parse(display_filter)
+ yield from self._filter_data(self._data, expressions)
diff --git a/setup.py b/setup.py
index 2e04169..9ccbab7 100644
--- a/setup.py
+++ b/setup.py
@@ -11,7 +11,7 @@
# This call to setup() does all the work
setup(
name="python-dict-display-filter",
- version="1.1.0",
+ version="1.2.0",
description="A Wireshark-like display filter for dictionaries.",
long_description=README,
long_description_content_type="text/markdown",
diff --git a/tests/test_object_display_filter.py b/tests/test_object_display_filter.py
new file mode 100644
index 0000000..80ce5bc
--- /dev/null
+++ b/tests/test_object_display_filter.py
@@ -0,0 +1,93 @@
+# vim: ts=8:sts=8:sw=8:noexpandtab
+#
+# This file is part of python-dict-display-filter.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see .
+import dataclasses
+import unittest
+from typing import List
+
+from parameterized import parameterized
+
+from pydictdisplayfilter.display_filters import ObjectDisplayFilter
+
+
+class Person:
+
+ def __init__(self, name: str = '', age: int = 0, gender: str = '', killed: bool = False, power: List = None):
+ self.name = name
+ self.age = age
+ self.gender = gender
+ self.killed = killed
+ self.power = power
+
+
+class TestObjectDisplayFilter(unittest.TestCase):
+ # List of objects representing the data to filter on.
+ data = [
+ Person(name="Morpheus", age=38, gender="male", killed=False),
+ Person(name="Neo", age=35, gender="male", killed=False, power=["flight", "bullet-time"]),
+ Person(name="Cipher", age=48, gender="male", killed=True),
+ Person(name="Trinity", age=32, gender="female", killed=False),
+ ]
+
+ @parameterized.expand([
+ # Field existence
+ ['name', 4],
+ ['power', 1],
+ ['not power', 3],
+ # Comparison operators
+ ['name == Neo', 1],
+ ['name == \x4e\x65\x6f', 1],
+ ['killed == True', 1],
+ ['gender == male', 3],
+ ['age == 32', 1],
+ ['age >= 32', 4],
+ ['age > 32', 3],
+ ['age <= 32', 1],
+ ['age <= 040', 1], # octal value
+ ['age <= 0x20', 1], # hexadecimal value
+ ['age < 32', 0],
+ ['age ~= 3', 3], # contains operator
+ ['age ~ 3', 3], # matches operator
+ ['age & 0x20', 4], # bitwise and operator
+ # In operator
+ ['age in { 32, 35, 38 }', 3],
+ ['age in { 30..40 }', 3],
+ ['age in { 30-40 }', 3],
+ ['age in { 30.0..40.0 }', 3],
+ ['name in { "Neo", "Trinity" }', 2],
+ # logical operators
+ ['age >= 32 and gender == male', 3],
+ ['name == Neo or name == Trinity', 2],
+ ['gender == female xor power', 2],
+ ['gender == male and (age > 30 and age < 40)', 2],
+ ['gender == male and not (age > 35)', 1],
+ ['gender == male and !(age > 35)', 1],
+ # Functions
+ ('len(name) == 3', 1),
+ ('upper(name) == NEO', 1),
+ ('lower(name) == neo', 1),
+ # Slice Operator
+ ('gender[0] == m', 3),
+ ('gender[-1] == e', 4),
+ ('gender[0:2] == ma', 3),
+ ('gender[:2] == ma', 3),
+ ('gender[2:] == le', 3),
+ ('gender[1-2] == ma', 3),
+ ('gender[0,1] == ma', 3),
+ ('gender[:2,3-4] == male', 3)
+ ])
+ def test_object_display_filter_returns_correct_number_of_items(self, display_filter, no_items):
+ self.assertEqual(len(list(ObjectDisplayFilter(self.data).filter(display_filter))), no_items)