From 4e78b9c12b059890ba1e6d644ecdae4344ee9975 Mon Sep 17 00:00:00 2001 From: Sven Heidemann Date: Sun, 25 Jul 2021 19:15:02 +0200 Subject: [PATCH] Added different queries & unittests --- src/cpl_query/_query/__init__.py | 0 src/cpl_query/_query/any_query.py | 7 ++ src/cpl_query/_query/first_query.py | 17 ++++ src/cpl_query/_query/for_each_query.py | 8 ++ src/cpl_query/_query/single_query.py | 21 +++++ src/cpl_query/_query/where_query.py | 13 +++ src/cpl_query/cpl_query.json | 2 +- src/cpl_query/extension/__init__.py | 0 src/cpl_query/extension/iterable_abc.py | 30 +++++++ src/cpl_query/extension/list.py | 40 +++++++++ src/cpl_query/main.py | 9 -- src/cpl_query/tests/__init__.py | 0 src/cpl_query/tests/models.py | 12 +++ src/cpl_query/tests/query_test.py | 111 ++++++++++++++++++++++++ src/cpl_query/tests/tester.py | 23 +++++ 15 files changed, 283 insertions(+), 10 deletions(-) create mode 100644 src/cpl_query/_query/__init__.py create mode 100644 src/cpl_query/_query/any_query.py create mode 100644 src/cpl_query/_query/first_query.py create mode 100644 src/cpl_query/_query/for_each_query.py create mode 100644 src/cpl_query/_query/single_query.py create mode 100644 src/cpl_query/_query/where_query.py create mode 100644 src/cpl_query/extension/__init__.py create mode 100644 src/cpl_query/extension/iterable_abc.py create mode 100644 src/cpl_query/extension/list.py delete mode 100644 src/cpl_query/main.py create mode 100644 src/cpl_query/tests/__init__.py create mode 100644 src/cpl_query/tests/models.py create mode 100644 src/cpl_query/tests/query_test.py create mode 100644 src/cpl_query/tests/tester.py diff --git a/src/cpl_query/_query/__init__.py b/src/cpl_query/_query/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/cpl_query/_query/any_query.py b/src/cpl_query/_query/any_query.py new file mode 100644 index 00000000..8ff9456e --- /dev/null +++ b/src/cpl_query/_query/any_query.py @@ -0,0 +1,7 @@ +from cpl_query._query.where_query import where_query +from cpl_query.extension.iterable_abc import IterableABC + + +def any_query(_list: IterableABC, _func: str) -> bool: + result = where_query(_list, _func) + return len(result) > 0 diff --git a/src/cpl_query/_query/first_query.py b/src/cpl_query/_query/first_query.py new file mode 100644 index 00000000..d0917117 --- /dev/null +++ b/src/cpl_query/_query/first_query.py @@ -0,0 +1,17 @@ +from typing import Optional + +from cpl_query.extension.iterable_abc import IterableABC + + +def first_query(_list: IterableABC) -> any: + if len(_list) == 0: + raise Exception('Index out of range') + + return _list[0] + + +def first_or_default_query(_list: IterableABC) -> Optional[any]: + if len(_list) == 0: + return None + + return _list[0] diff --git a/src/cpl_query/_query/for_each_query.py b/src/cpl_query/_query/for_each_query.py new file mode 100644 index 00000000..6681bd0f --- /dev/null +++ b/src/cpl_query/_query/for_each_query.py @@ -0,0 +1,8 @@ +from collections import Callable + +from cpl_query.extension.iterable_abc import IterableABC + + +def for_each_query(_list: IterableABC, func: Callable): + for element in _list: + func(element) diff --git a/src/cpl_query/_query/single_query.py b/src/cpl_query/_query/single_query.py new file mode 100644 index 00000000..8ad30cef --- /dev/null +++ b/src/cpl_query/_query/single_query.py @@ -0,0 +1,21 @@ +from typing import Optional + +from cpl_query.extension.iterable_abc import IterableABC + + +def single_query(_list: IterableABC) -> any: + if len(_list) > 1: + raise Exception('Found more than one element') + elif len(_list) == 0: + raise Exception('Found no element') + + return _list[0] + + +def single_or_default_query(_list: IterableABC) -> Optional[any]: + if len(_list) > 1: + raise Exception('Index out of range') + elif len(_list) == 0: + return None + + return _list[0] diff --git a/src/cpl_query/_query/where_query.py b/src/cpl_query/_query/where_query.py new file mode 100644 index 00000000..97e3eefc --- /dev/null +++ b/src/cpl_query/_query/where_query.py @@ -0,0 +1,13 @@ +from cpl_query.extension.iterable_abc import IterableABC + + +def where_query(_list: IterableABC, _func: str) -> IterableABC: + result = IterableABC() + for element in _list: + element_type = type(element).__name__ + if element_type in _func: + func = _func.replace(element_type, 'element') + if eval(func): + result.append(element) + + return result diff --git a/src/cpl_query/cpl_query.json b/src/cpl_query/cpl_query.json index 1603c0c7..85334a56 100644 --- a/src/cpl_query/cpl_query.json +++ b/src/cpl_query/cpl_query.json @@ -20,7 +20,7 @@ ], "PythonVersion": ">=3.9.2", "PythonPath": { - "linux": "../../cpl-env/bin/python3.9" + "linux": "../cpl-env/bin/python3.9" }, "Classifiers": [] }, diff --git a/src/cpl_query/extension/__init__.py b/src/cpl_query/extension/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/cpl_query/extension/iterable_abc.py b/src/cpl_query/extension/iterable_abc.py new file mode 100644 index 00000000..0aceedf1 --- /dev/null +++ b/src/cpl_query/extension/iterable_abc.py @@ -0,0 +1,30 @@ +from abc import ABC, abstractmethod +from typing import Optional, Callable + + +class IterableABC(ABC, list): + + @abstractmethod + def __init__(self): + list.__init__(self) + + @abstractmethod + def any(self, func: str) -> bool: pass + + @abstractmethod + def first(self) -> any: pass + + @abstractmethod + def first_or_default(self) -> any: pass + + @abstractmethod + def for_each(self, func: Callable): pass + + @abstractmethod + def single(self): pass + + @abstractmethod + def single_or_default(self) -> Optional[any]: pass + + @abstractmethod + def where(self, func: str) -> 'IterableABC': pass diff --git a/src/cpl_query/extension/list.py b/src/cpl_query/extension/list.py new file mode 100644 index 00000000..b8eebcf3 --- /dev/null +++ b/src/cpl_query/extension/list.py @@ -0,0 +1,40 @@ +from typing import Optional, Callable + +from .._query.any_query import any_query +from .._query.first_query import first_or_default_query, first_query +from .._query.for_each_query import for_each_query +from .._query.single_query import single_query, single_or_default_query +from .._query.where_query import where_query +from cpl_query.extension.iterable_abc import IterableABC + + +class List(IterableABC): + + def __init__(self): + IterableABC.__init__(self) + + def any(self, func: str) -> bool: + return any_query(self, func) + + def first(self) -> any: + return first_query(self) + + def first_or_default(self) -> Optional[any]: + return first_or_default_query(self) + + def for_each(self, func: Callable): + for_each_query(self, func) + + def single(self) -> any: + return single_query(self) + + def single_or_default(self) -> Optional[any]: + return single_or_default_query(self) + + def where(self, func: str) -> IterableABC: + res = where_query(self, func) + res.__class__ = List + return res + + def to_list(self) -> list: + return list(self) diff --git a/src/cpl_query/main.py b/src/cpl_query/main.py deleted file mode 100644 index 42e34771..00000000 --- a/src/cpl_query/main.py +++ /dev/null @@ -1,9 +0,0 @@ -from cpl.console import Console - - -def main(): - Console.write_line('Hello World') - - -if __name__ == '__main__': - main() diff --git a/src/cpl_query/tests/__init__.py b/src/cpl_query/tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/cpl_query/tests/models.py b/src/cpl_query/tests/models.py new file mode 100644 index 00000000..d3b147dd --- /dev/null +++ b/src/cpl_query/tests/models.py @@ -0,0 +1,12 @@ +class User: + + def __init__(self, name, address): + self.name = name + self.address = address + + +class Address: + + def __init__(self, street, nr): + self.street = street + self.nr = nr \ No newline at end of file diff --git a/src/cpl_query/tests/query_test.py b/src/cpl_query/tests/query_test.py new file mode 100644 index 00000000..c85c2dec --- /dev/null +++ b/src/cpl_query/tests/query_test.py @@ -0,0 +1,111 @@ +import string +import unittest +from random import randint + +from cpl.utils import String +from cpl_query.extension.list import List +from cpl_query.tests.models import User, Address + + +class QueryTest(unittest.TestCase): + + def setUp(self) -> None: + self._tests = List() + self._t_user = User( + 'Test user', + Address( + 'teststr.', + 15 + ) + ) + + self._generate_test_data() + + def _generate_test_data(self): + for i in range(0, 100): + user = User( + String.random_string(string.ascii_letters, 8), + Address( + String.random_string(string.ascii_letters, 10), + randint(0, 10) + ) + ) + + self._tests.append(user) + + self._tests.append(self._t_user) + + def test_any(self): + results = [] + for user in self._tests: + if user.address.nr == 10: + results.append(user) + + res = self._tests.any(f'User.address.nr == 10') + n_res = self._tests.any(f'User.address.nr == 100') + + self.assertTrue(res) + self.assertFalse(n_res) + + def test_first(self): + results = [] + for user in self._tests: + if user.address.nr == 10: + results.append(user) + + res = self._tests.where(f'User.address.nr == 10') + s_res = self._tests.where(f'User.address.nr == 10').first() + + self.assertEqual(len(res), len(results)) + self.assertIsNotNone(s_res) + + def test_first_or_default(self): + results = [] + for user in self._tests: + if user.address.nr == 10: + results.append(user) + + res = self._tests.where(f'User.address.nr == 10') + s_res = self._tests.where(f'User.address.nr == 10').first_or_default() + sn_res = self._tests.where(f'User.address.nr == 11').first_or_default() + + self.assertEqual(len(res), len(results)) + self.assertIsNotNone(s_res) + self.assertIsNone(sn_res) + + def test_for_each(self): + users = [] + self._tests.for_each( + lambda user: ( + # Console.write_line(f'User: {user.name} | '), + # Console.write(f'Address: {user.address.street}'), + users.append(user) + ) + ) + + self.assertEqual(len(users), len(self._tests)) + + def test_single(self): + res = self._tests.where(f'User.address.nr == {self._t_user.address.nr}') + s_res = self._tests.where(f'User.address.nr == {self._t_user.address.nr}').single() + + self.assertEqual(len(res), 1) + self.assertEqual(self._t_user, s_res) + + def test_single_or_default(self): + res = self._tests.where(f'User.address.nr == {self._t_user.address.nr}') + s_res = self._tests.where(f'User.address.nr == {self._t_user.address.nr}').single_or_default() + sn_res = self._tests.where(f'User.address.nr == {self._t_user.address.nr + 1}').single_or_default() + + self.assertEqual(len(res), 1) + self.assertEqual(self._t_user, s_res) + self.assertIsNone(sn_res) + + def test_where(self): + results = [] + for user in self._tests: + if user.address.nr == 5: + results.append(user) + + res = self._tests.where('User.address.nr == 5') + self.assertEqual(len(results), len(res)) diff --git a/src/cpl_query/tests/tester.py b/src/cpl_query/tests/tester.py new file mode 100644 index 00000000..98b10ebb --- /dev/null +++ b/src/cpl_query/tests/tester.py @@ -0,0 +1,23 @@ +import unittest + +from cpl_query.tests.query_test import QueryTest + + +class Tester: + + def __init__(self): + self._suite = unittest.TestSuite() + + def create(self): + loader = unittest.TestLoader() + self._suite.addTests(loader.loadTestsFromTestCase(QueryTest)) + + def start(self): + runner = unittest.TextTestRunner() + runner.run(self._suite) + + +if __name__ == '__main__': + tester = Tester() + tester.create() + tester.start()