From d8f7e0381572ad38c9f5b8a326c8bfa18fc1bf6e Mon Sep 17 00:00:00 2001 From: Sven Heidemann Date: Thu, 1 Dec 2022 16:27:29 +0100 Subject: [PATCH 1/6] Added group by & refactored Iterable #129 --- src/cpl_query/base/default_lambda.py | 2 + src/cpl_query/base/queryable_abc.py | 9 + src/cpl_query/iterable/iterable.py | 191 ++++++------------ unittests/unittests/application.py | 4 +- .../iterable_query_test_case.py | 38 +++- 5 files changed, 111 insertions(+), 133 deletions(-) create mode 100644 src/cpl_query/base/default_lambda.py diff --git a/src/cpl_query/base/default_lambda.py b/src/cpl_query/base/default_lambda.py new file mode 100644 index 00000000..3d985ff2 --- /dev/null +++ b/src/cpl_query/base/default_lambda.py @@ -0,0 +1,2 @@ +def default_lambda(x: object): + return x diff --git a/src/cpl_query/base/queryable_abc.py b/src/cpl_query/base/queryable_abc.py index 976c9ab4..fa22ab83 100644 --- a/src/cpl_query/base/queryable_abc.py +++ b/src/cpl_query/base/queryable_abc.py @@ -155,6 +155,15 @@ class QueryableABC(ABC): """ pass + def group_by(self, _func: Callable = None) -> 'QueryableABC': + r"""Groups by func + + Returns + ------- + Grouped list[list[any]]: any + """ + pass + @abstractmethod def last(self) -> any: r"""Returns last element diff --git a/src/cpl_query/iterable/iterable.py b/src/cpl_query/iterable/iterable.py index c8e54573..6f7ce73f 100644 --- a/src/cpl_query/iterable/iterable.py +++ b/src/cpl_query/iterable/iterable.py @@ -16,59 +16,36 @@ class Iterable(IterableABC): IterableABC.__init__(self, t, values) def all(self, _func: Callable = None) -> bool: - if self is None: - raise ArgumentNoneException(ExceptionArgument.list) - if _func is None: _func = _default_lambda - result = self.where(_func) - return len(result) == len(self) + return self.where(_func).count() == self.count() def any(self, _func: Callable = None) -> bool: - if self is None: - raise ArgumentNoneException(ExceptionArgument.list) - if _func is None: _func = _default_lambda - result = self.where(_func) - return len(result) > 0 + return self.where(_func).count() > 0 def average(self, _func: Callable = None) -> Union[int, float, complex]: - if self is None: - raise ArgumentNoneException(ExceptionArgument.list) - if _func is None and not is_number(self.type): raise InvalidTypeException() - if _func is None: - _func = _default_lambda - - return float(self.sum(_func)) / float(self.count()) + return self.sum(_func) / self.count() def contains(self, _value: object) -> bool: - if self is None: - raise ArgumentNoneException(ExceptionArgument.list) - if _value is None: raise ArgumentNoneException(ExceptionArgument.value) return self.where(lambda x: x == _value).count() > 0 def count(self, _func: Callable = None) -> int: - if self is None: - raise ArgumentNoneException(ExceptionArgument.list) - if _func is None: - return len(self) + return self.__len__() - return len(self.where(_func)) - - def distinct(self, _func: Callable = None) -> IterableABC: - if self is None: - raise ArgumentNoneException(ExceptionArgument.list) + return self.where(_func).__len__() + def distinct(self, _func: Callable = None) -> 'Iterable': if _func is None: _func = _default_lambda @@ -85,18 +62,12 @@ class Iterable(IterableABC): return result def element_at(self, _index: int) -> any: - if self is None: - raise ArgumentNoneException(ExceptionArgument.list) - if _index is None: raise ArgumentNoneException(ExceptionArgument.index) return self[_index] def element_at_or_default(self, _index: int) -> any: - if self is None: - raise ArgumentNoneException(ExceptionArgument.list) - if _index is None: raise ArgumentNoneException(ExceptionArgument.index) @@ -105,56 +76,52 @@ class Iterable(IterableABC): except IndexError: return None - def first(self: IterableABC) -> any: - if self is None: - raise ArgumentNoneException(ExceptionArgument.list) - + def first(self) -> any: if len(self) == 0: raise IndexOutOfRangeException() return self[0] - def first_or_default(self: IterableABC) -> Optional[any]: - if self is None: - raise ArgumentNoneException(ExceptionArgument.list) - + def first_or_default(self) -> Optional[any]: if len(self) == 0: return None return self[0] - def last(self: IterableABC) -> any: - if self is None: - raise ArgumentNoneException(ExceptionArgument.list) - - if len(self) == 0: - raise IndexOutOfRangeException() - - return self[len(self) - 1] - - def last_or_default(self: IterableABC) -> Optional[any]: - if self is None: - raise ArgumentNoneException(ExceptionArgument.list) - - if len(self) == 0: - return None - - return self[len(self) - 1] - - def for_each(self, _func: Callable = None): - if self is None: - raise ArgumentNoneException(ExceptionArgument.list) - + def group_by(self, _func: Callable = None) -> 'Iterable': if _func is None: _func = _default_lambda + groups = {} - for element in self: - _func(element) + for v in self: + value = _func(v) + if v not in groups: + groups[value] = Iterable(type(v)) + + groups[value].append(v) + + return Iterable(Iterable).extend(groups.values()) + + def last(self) -> any: + if len(self) == 0: + raise IndexOutOfRangeException() + + return self[len(self) - 1] + + def last_or_default(self) -> Optional[any]: + if len(self) == 0: + return None + + return self[len(self) - 1] + + def for_each(self, _func: Callable = None) -> 'Iterable': + if _func is not None: + for element in self: + _func(element) + + return self def max(self, _func: Callable = None) -> Union[int, float, complex]: - if self is None: - raise ArgumentNoneException(ExceptionArgument.list) - if _func is None and not is_number(self.type): raise InvalidTypeException() @@ -166,6 +133,7 @@ class Iterable(IterableABC): def median(self, _func=None) -> Union[int, float]: if _func is None: _func = _default_lambda + result = self.order_by(_func).select(_func).to_list() length = len(result) i = int(length / 2) @@ -176,9 +144,6 @@ class Iterable(IterableABC): ) def min(self, _func: Callable = None) -> Union[int, float, complex]: - if self is None: - raise ArgumentNoneException(ExceptionArgument.list) - if _func is None and not is_number(self.type): raise InvalidTypeException() @@ -188,9 +153,6 @@ class Iterable(IterableABC): return _func(min(self, key=_func)) def order_by(self, _func: Callable = None) -> OrderedIterableABC: - if self is None: - raise ArgumentNoneException(ExceptionArgument.list) - if _func is None: _func = _default_lambda @@ -198,22 +160,16 @@ class Iterable(IterableABC): return OrderedIterable(self.type, _func, sorted(self, key=_func)) def order_by_descending(self, _func: Callable = None) -> OrderedIterableABC: - if self is None: - raise ArgumentNoneException(ExceptionArgument.list) - if _func is None: _func = _default_lambda from cpl_query.iterable.ordered_iterable import OrderedIterable return OrderedIterable(self.type, _func, sorted(self, key=_func, reverse=True)) - def reverse(self: IterableABC) -> IterableABC: - if self is None: - raise ArgumentNoneException(ExceptionArgument.list) + def reverse(self) -> 'Iterable': + return Iterable().extend(reversed(self)) - return Iterable().extend(reversed(self.to_list())) - - def select(self, _func: Callable = None) -> IterableABC: + def select(self, _func: Callable = None) -> 'Iterable': if _func is None: _func = _default_lambda @@ -221,10 +177,7 @@ class Iterable(IterableABC): result.extend(_func(_o) for _o in self) return result - def select_many(self, _func: Callable = None) -> IterableABC: - if _func is None: - _func = _default_lambda - + def select_many(self, _func: Callable = None) -> 'Iterable': result = Iterable() # The line below is pain. I don't understand anything of it... # written on 09.11.2022 by Sven Heidemann @@ -233,10 +186,7 @@ class Iterable(IterableABC): result.extend(elements) return result - def single(self: IterableABC) -> any: - if self is None: - raise ArgumentNoneException(ExceptionArgument.list) - + def single(self) -> any: if len(self) > 1: raise Exception('Found more than one element') elif len(self) == 0: @@ -244,10 +194,7 @@ class Iterable(IterableABC): return self[0] - def single_or_default(self: IterableABC) -> Optional[any]: - if self is None: - raise ArgumentNoneException(ExceptionArgument.list) - + def single_or_default(self) -> Optional[any]: if len(self) > 1: raise Exception('Index out of range') elif len(self) == 0: @@ -255,19 +202,13 @@ class Iterable(IterableABC): return self[0] - def skip(self, _index: int) -> IterableABC: - if self is None: - raise ArgumentNoneException(ExceptionArgument.list) - + def skip(self, _index: int) -> 'Iterable': if _index is None: raise ArgumentNoneException(ExceptionArgument.index) return Iterable(self.type, values=self[_index:]) - def skip_last(self, _index: int) -> IterableABC: - if self is None: - raise ArgumentNoneException(ExceptionArgument.list) - + def skip_last(self, _index: int) -> 'Iterable': if _index is None: raise ArgumentNoneException(ExceptionArgument.index) @@ -277,10 +218,20 @@ class Iterable(IterableABC): result.extend(self[:index]) return result - def take(self, _index: int) -> IterableABC: - if self is None: - raise ArgumentNoneException(ExceptionArgument.list) + def sum(self, _func: Callable = None) -> Union[int, float, complex]: + if _func is None and not is_number(self.type): + raise InvalidTypeException() + if _func is None: + _func = _default_lambda + + result = 0 + for x in self: + result += _func(x) + + return result + + def take(self, _index: int) -> 'Iterable': if _index is None: raise ArgumentNoneException(ExceptionArgument.index) @@ -288,10 +239,7 @@ class Iterable(IterableABC): result.extend(self[:_index]) return result - def take_last(self, _index: int) -> IterableABC: - if self is None: - raise ArgumentNoneException(ExceptionArgument.list) - + def take_last(self, _index: int) -> 'Iterable': index = len(self) - _index if index >= len(self) or index < 0: @@ -301,22 +249,7 @@ class Iterable(IterableABC): result.extend(self[index:]) return result - def sum(self, _func: Callable = None) -> Union[int, float, complex]: - if self is None: - raise ArgumentNoneException(ExceptionArgument.list) - - if _func is None and not is_number(self.type): - raise InvalidTypeException() - - if _func is None: - _func = _default_lambda - - return sum([_func(x) for x in self]) - - def where(self, _func: Callable = None) -> IterableABC: - if self is None: - raise ArgumentNoneException(ExceptionArgument.list) - + def where(self, _func: Callable = None) -> 'Iterable': if _func is None: raise ArgumentNoneException(ExceptionArgument.func) @@ -328,4 +261,4 @@ class Iterable(IterableABC): if _func(element): result.append(element) - return result \ No newline at end of file + return result diff --git a/unittests/unittests/application.py b/unittests/unittests/application.py index 0ba34799..c9547c15 100644 --- a/unittests/unittests/application.py +++ b/unittests/unittests/application.py @@ -18,6 +18,6 @@ class Application(ApplicationABC): def main(self): runner = unittest.TextTestRunner() - runner.run(CLITestSuite()) + # runner.run(CLITestSuite()) runner.run(QueryTestSuite()) - runner.run(TranslationTestSuite()) + # runner.run(TranslationTestSuite()) diff --git a/unittests/unittests_query/iterable_query_test_case.py b/unittests/unittests_query/iterable_query_test_case.py index 626a4905..c48fc9ea 100644 --- a/unittests/unittests_query/iterable_query_test_case.py +++ b/unittests/unittests_query/iterable_query_test_case.py @@ -5,6 +5,7 @@ from random import randint from cpl_core.utils import String from cpl_query.exceptions import InvalidTypeException, ArgumentNoneException from cpl_query.extension.list import List +from cpl_query.iterable import Iterable from unittests_query.models import User, Address @@ -102,8 +103,18 @@ class IterableQueryTestCase(unittest.TestCase): self.assertEqual(1, self._tests.count(lambda u: u == self._t_user)) def test_distinct(self): - res = self._tests.distinct(lambda u: u.address.nr).where(lambda u: u.address.nr == 5) - self.assertEqual(1, len(res)) + res = self._tests.select(lambda u: u.address.nr).where(lambda a: a == 5).distinct() + self.assertEqual(1, res.count()) + + addresses = [] + for u in self._tests: + if u.address.nr in addresses: + continue + + addresses.append(u.address.nr) + + res2 = self._tests.distinct(lambda x: x.address.nr).select(lambda x: x.address.nr) + self.assertEqual(addresses, res2) def test_element_at(self): index = randint(0, len(self._tests) - 1) @@ -168,6 +179,29 @@ class IterableQueryTestCase(unittest.TestCase): self.assertEqual(res[0], s_res) self.assertIsNone(sn_res) + def test_group_by(self): + def by_adr(u): + return u.address.nr + + t = self._tests.select(by_adr).group_by() + res = self._tests.group_by(by_adr) + self.assertTrue(isinstance(res.first_or_default(), Iterable)) + self.assertNotEqual(self._tests.count(), res.count()) + self.assertEqual(self._tests.distinct(by_adr).count(), res.count()) + + elements = List(int) + groups = {} + for x in range(0, 1000): + v = randint(1, 100) + if v not in groups: + groups[v] = [] + + groups[v].append(v) + elements.append(v) + + r1, r2 = list(groups.values()), elements.group_by() + self.assertEqual(r1, r2) + def test_for_each(self): users = [] self._tests.for_each(lambda user: ( -- 2.45.2 From e8ae635c88170351aef35d31e21c636234080a79 Mon Sep 17 00:00:00 2001 From: Sven Heidemann Date: Thu, 1 Dec 2022 17:00:17 +0100 Subject: [PATCH 2/6] Improved tests #129 --- unittests/unittests/application.py | 4 ++-- unittests/unittests_cli/add_test_case.py | 5 ++++- unittests/unittests_cli/build_test_case.py | 5 ++++- unittests/unittests_cli/cli_test_suite.py | 2 +- unittests/unittests_cli/constants.py | 10 +++++++--- unittests/unittests_cli/install_test_case.py | 5 ++++- unittests/unittests_cli/new_test_case.py | 5 ++++- unittests/unittests_cli/publish_test_case.py | 5 ++++- unittests/unittests_cli/remove_test_case.py | 5 ++++- unittests/unittests_cli/run_test_case.py | 5 ++++- unittests/unittests_cli/start_test_case.py | 5 ++++- unittests/unittests_cli/uninstall_test_case.py | 5 ++++- unittests/unittests_query/query_test_suite.py | 2 +- 13 files changed, 47 insertions(+), 16 deletions(-) diff --git a/unittests/unittests/application.py b/unittests/unittests/application.py index c9547c15..0ba34799 100644 --- a/unittests/unittests/application.py +++ b/unittests/unittests/application.py @@ -18,6 +18,6 @@ class Application(ApplicationABC): def main(self): runner = unittest.TextTestRunner() - # runner.run(CLITestSuite()) + runner.run(CLITestSuite()) runner.run(QueryTestSuite()) - # runner.run(TranslationTestSuite()) + runner.run(TranslationTestSuite()) diff --git a/unittests/unittests_cli/add_test_case.py b/unittests/unittests_cli/add_test_case.py index 27964fbe..d1b64503 100644 --- a/unittests/unittests_cli/add_test_case.py +++ b/unittests/unittests_cli/add_test_case.py @@ -26,7 +26,10 @@ class AddTestCase(unittest.TestCase): return project_json def setUp(self): - os.chdir(os.path.abspath(PLAYGROUND_PATH)) + if not os.path.exists(PLAYGROUND_PATH): + os.makedirs(PLAYGROUND_PATH) + + os.chdir(PLAYGROUND_PATH) # create projects CLICommands.new('console', self._source, '--ab', '--s') os.chdir(os.path.join(os.getcwd(), self._source)) diff --git a/unittests/unittests_cli/build_test_case.py b/unittests/unittests_cli/build_test_case.py index e35d5cbd..d01de06f 100644 --- a/unittests/unittests_cli/build_test_case.py +++ b/unittests/unittests_cli/build_test_case.py @@ -31,7 +31,10 @@ class BuildTestCase(unittest.TestCase): project_file.close() def setUp(self): - os.chdir(os.path.abspath(PLAYGROUND_PATH)) + if not os.path.exists(PLAYGROUND_PATH): + os.makedirs(PLAYGROUND_PATH) + + os.chdir(PLAYGROUND_PATH) # create projects CLICommands.new('console', self._source, '--ab', '--s') os.chdir(os.path.join(os.getcwd(), self._source)) diff --git a/unittests/unittests_cli/cli_test_suite.py b/unittests/unittests_cli/cli_test_suite.py index 65cb6355..228fa366 100644 --- a/unittests/unittests_cli/cli_test_suite.py +++ b/unittests/unittests_cli/cli_test_suite.py @@ -75,4 +75,4 @@ class CLITestSuite(unittest.TestSuite): def run(self, *args): self._setup() self._result = super().run(*args) - # self._cleanup() + self._cleanup() diff --git a/unittests/unittests_cli/constants.py b/unittests/unittests_cli/constants.py index f585a27f..c8318dab 100644 --- a/unittests/unittests_cli/constants.py +++ b/unittests/unittests_cli/constants.py @@ -1,5 +1,9 @@ import os -PLAYGROUND_PATH = os.path.abspath(os.path.join(os.getcwd(), '../test_cli_playground')) -TRANSLATION_PATH = os.path.abspath(os.path.join(os.getcwd(), '../unittests_translation')) -CLI_PATH = os.path.abspath(os.path.join(os.getcwd(), '../../src/cpl_cli/main.py')) +base = '' +if not os.getcwd().endswith('unittests'): + base = '../' + +PLAYGROUND_PATH = os.path.abspath(os.path.join(os.getcwd(), f'{base}test_cli_playground')) +TRANSLATION_PATH = os.path.abspath(os.path.join(os.getcwd(), f'{base}unittests_translation')) +CLI_PATH = os.path.abspath(os.path.join(os.getcwd(), f'{base}../src/cpl_cli/main.py')) diff --git a/unittests/unittests_cli/install_test_case.py b/unittests/unittests_cli/install_test_case.py index 527256d1..516a44e4 100644 --- a/unittests/unittests_cli/install_test_case.py +++ b/unittests/unittests_cli/install_test_case.py @@ -31,7 +31,10 @@ class InstallTestCase(unittest.TestCase): project_file.close() def setUp(self): - os.chdir(os.path.abspath(PLAYGROUND_PATH)) + if not os.path.exists(PLAYGROUND_PATH): + os.makedirs(PLAYGROUND_PATH) + + os.chdir(PLAYGROUND_PATH) # create projects CLICommands.new('console', self._source, '--ab', '--s') os.chdir(os.path.join(os.getcwd(), self._source)) diff --git a/unittests/unittests_cli/new_test_case.py b/unittests/unittests_cli/new_test_case.py index 5c05334e..5d0eb0a3 100644 --- a/unittests/unittests_cli/new_test_case.py +++ b/unittests/unittests_cli/new_test_case.py @@ -10,7 +10,10 @@ from unittests_shared.cli_commands import CLICommands class NewTestCase(unittest.TestCase): def setUp(self): - os.chdir(os.path.abspath(PLAYGROUND_PATH)) + if not os.path.exists(PLAYGROUND_PATH): + os.makedirs(PLAYGROUND_PATH) + + os.chdir(PLAYGROUND_PATH) def _test_project(self, project_type: str, name: str, *args, test_venv=False, without_ws=False): CLICommands.new(project_type, name, *args) diff --git a/unittests/unittests_cli/publish_test_case.py b/unittests/unittests_cli/publish_test_case.py index 7c828ced..141e374e 100644 --- a/unittests/unittests_cli/publish_test_case.py +++ b/unittests/unittests_cli/publish_test_case.py @@ -31,7 +31,10 @@ class PublishTestCase(unittest.TestCase): project_file.close() def setUp(self): - os.chdir(os.path.abspath(PLAYGROUND_PATH)) + if not os.path.exists(PLAYGROUND_PATH): + os.makedirs(PLAYGROUND_PATH) + + os.chdir(PLAYGROUND_PATH) # create projects CLICommands.new('console', self._source, '--ab', '--s') os.chdir(os.path.join(os.getcwd(), self._source)) diff --git a/unittests/unittests_cli/remove_test_case.py b/unittests/unittests_cli/remove_test_case.py index 81aa8ee0..f618e4b8 100644 --- a/unittests/unittests_cli/remove_test_case.py +++ b/unittests/unittests_cli/remove_test_case.py @@ -24,7 +24,10 @@ class RemoveTestCase(unittest.TestCase): return project_json def setUp(self): - os.chdir(os.path.abspath(PLAYGROUND_PATH)) + if not os.path.exists(PLAYGROUND_PATH): + os.makedirs(PLAYGROUND_PATH) + + os.chdir(PLAYGROUND_PATH) # create projects CLICommands.new('console', self._source, '--ab', '--s') os.chdir(os.path.join(os.getcwd(), self._source)) diff --git a/unittests/unittests_cli/run_test_case.py b/unittests/unittests_cli/run_test_case.py index 1d86b68d..7ec9b9dd 100644 --- a/unittests/unittests_cli/run_test_case.py +++ b/unittests/unittests_cli/run_test_case.py @@ -50,7 +50,10 @@ class RunTestCase(unittest.TestCase): project_file.close() def setUp(self): - os.chdir(os.path.abspath(PLAYGROUND_PATH)) + if not os.path.exists(PLAYGROUND_PATH): + os.makedirs(PLAYGROUND_PATH) + + os.chdir(PLAYGROUND_PATH) # create projects CLICommands.new('console', self._source, '--ab', '--s') os.chdir(os.path.join(os.getcwd(), self._source)) diff --git a/unittests/unittests_cli/start_test_case.py b/unittests/unittests_cli/start_test_case.py index f41eb450..3ff09294 100644 --- a/unittests/unittests_cli/start_test_case.py +++ b/unittests/unittests_cli/start_test_case.py @@ -50,7 +50,10 @@ class StartTestCase(unittest.TestCase): project_file.close() def setUp(self): - os.chdir(os.path.abspath(PLAYGROUND_PATH)) + if not os.path.exists(PLAYGROUND_PATH): + os.makedirs(PLAYGROUND_PATH) + + os.chdir(PLAYGROUND_PATH) # create projects CLICommands.new('console', self._source, '--ab', '--s') os.chdir(os.path.join(os.getcwd(), self._source)) diff --git a/unittests/unittests_cli/uninstall_test_case.py b/unittests/unittests_cli/uninstall_test_case.py index d81bddf9..a552bfc2 100644 --- a/unittests/unittests_cli/uninstall_test_case.py +++ b/unittests/unittests_cli/uninstall_test_case.py @@ -29,7 +29,10 @@ class UninstallTestCase(unittest.TestCase): return project_json def setUp(self): - os.chdir(os.path.abspath(PLAYGROUND_PATH)) + if not os.path.exists(PLAYGROUND_PATH): + os.makedirs(PLAYGROUND_PATH) + + os.chdir(PLAYGROUND_PATH) # create projects CLICommands.new('console', self._source, '--ab', '--s') os.chdir(os.path.join(os.getcwd(), self._source)) diff --git a/unittests/unittests_query/query_test_suite.py b/unittests/unittests_query/query_test_suite.py index e1c307b4..c8b0d7c1 100644 --- a/unittests/unittests_query/query_test_suite.py +++ b/unittests/unittests_query/query_test_suite.py @@ -25,4 +25,4 @@ class QueryTestSuite(unittest.TestSuite): if __name__ == "__main__": runner = unittest.TextTestRunner() - runner.run(QueryTestSuite()) \ No newline at end of file + runner.run(QueryTestSuite()) -- 2.45.2 From f0f79e7e3bebbad3834d3a7f17a1ae2f44202527 Mon Sep 17 00:00:00 2001 From: Sven Heidemann Date: Thu, 1 Dec 2022 21:09:39 +0100 Subject: [PATCH 3/6] Refactored cpl-query Iterable & Enumerable #129 --- src/cpl_query/base/ordered_queryable.py | 34 ++ .../ordered_queryable_abc.py} | 12 +- src/cpl_query/base/queryable_abc.py | 292 ++++++++++++----- src/cpl_query/base/sequence_abc.py | 86 +++++ src/cpl_query/base/sequence_values.py | 10 +- src/cpl_query/enumerable/__init__.py | 3 - src/cpl_query/enumerable/enumerable.py | 296 +----------------- src/cpl_query/enumerable/enumerable_abc.py | 81 +---- .../enumerable/ordered_enumerable.py | 36 --- .../enumerable/ordered_enumerable_abc.py | 43 --- src/cpl_query/iterable/__init__.py | 2 - src/cpl_query/iterable/iterable.py | 253 +-------------- src/cpl_query/iterable/iterable_abc.py | 47 ++- src/cpl_query/iterable/ordered_iterable.py | 35 --- unittests/unittests_cli/constants.py | 2 +- .../enumerable_query_test_case.py | 29 +- .../unittests_query/enumerable_test_case.py | 33 +- .../iterable_query_test_case.py | 4 +- unittests/unittests_query/models.py | 8 +- 19 files changed, 390 insertions(+), 916 deletions(-) create mode 100644 src/cpl_query/base/ordered_queryable.py rename src/cpl_query/{iterable/ordered_iterable_abc.py => base/ordered_queryable_abc.py} (69%) create mode 100644 src/cpl_query/base/sequence_abc.py delete mode 100644 src/cpl_query/enumerable/ordered_enumerable.py delete mode 100644 src/cpl_query/enumerable/ordered_enumerable_abc.py delete mode 100644 src/cpl_query/iterable/ordered_iterable.py diff --git a/src/cpl_query/base/ordered_queryable.py b/src/cpl_query/base/ordered_queryable.py new file mode 100644 index 00000000..c7b3dbf7 --- /dev/null +++ b/src/cpl_query/base/ordered_queryable.py @@ -0,0 +1,34 @@ +from collections.abc import Callable + +from cpl_query.base.ordered_queryable_abc import OrderedQueryableABC +from cpl_query.exceptions import ArgumentNoneException, ExceptionArgument +from cpl_query.iterable.iterable import Iterable + + +class OrderedQueryable(OrderedQueryableABC): + r"""Implementation of :class: `cpl_query.extension.Iterable` `cpl_query.extension.OrderedIterableABC` + """ + + def __init__(self, _t: type, _values: Iterable = None, _func: Callable = None): + OrderedQueryableABC.__init__(self, _t, _values, _func) + + def then_by(self: OrderedQueryableABC, _func: Callable) -> OrderedQueryableABC: + if self is None: + raise ArgumentNoneException(ExceptionArgument.list) + + if _func is None: + raise ArgumentNoneException(ExceptionArgument.func) + + self._funcs.append(_func) + + return OrderedQueryable(self.type, sorted(self, key=lambda *args: [f(*args) for f in self._funcs]), _func) + + def then_by_descending(self: OrderedQueryableABC, _func: Callable) -> OrderedQueryableABC: + if self is None: + raise ArgumentNoneException(ExceptionArgument.list) + + if _func is None: + raise ArgumentNoneException(ExceptionArgument.func) + + self._funcs.append(_func) + return OrderedQueryable(self.type, sorted(self, key=lambda *args: [f(*args) for f in self._funcs], reverse=True), _func) diff --git a/src/cpl_query/iterable/ordered_iterable_abc.py b/src/cpl_query/base/ordered_queryable_abc.py similarity index 69% rename from src/cpl_query/iterable/ordered_iterable_abc.py rename to src/cpl_query/base/ordered_queryable_abc.py index eba32559..f3ec56fc 100644 --- a/src/cpl_query/iterable/ordered_iterable_abc.py +++ b/src/cpl_query/base/ordered_queryable_abc.py @@ -2,20 +2,20 @@ from abc import abstractmethod from collections.abc import Callable from typing import Iterable -from cpl_query.iterable.iterable_abc import IterableABC +from cpl_query.base.queryable_abc import QueryableABC -class OrderedIterableABC(IterableABC): +class OrderedQueryableABC(QueryableABC): @abstractmethod - def __init__(self, _t: type, _func: Callable = None, _values: Iterable = None): - IterableABC.__init__(self, _t, _values) + def __init__(self, _t: type, _values: Iterable = None, _func: Callable = None): + QueryableABC.__init__(self, _t, _values) self._funcs: list[Callable] = [] if _func is not None: self._funcs.append(_func) @abstractmethod - def then_by(self, func: Callable) -> 'OrderedIterableABC': + def then_by(self, func: Callable) -> 'OrderedQueryableABC': r"""Sorts OrderedList in ascending order by function Parameter @@ -29,7 +29,7 @@ class OrderedIterableABC(IterableABC): pass @abstractmethod - def then_by_descending(self, func: Callable) -> 'OrderedIterableABC': + def then_by_descending(self, func: Callable) -> 'OrderedQueryableABC': r"""Sorts OrderedList in descending order by function Parameter diff --git a/src/cpl_query/base/queryable_abc.py b/src/cpl_query/base/queryable_abc.py index fa22ab83..117397c6 100644 --- a/src/cpl_query/base/queryable_abc.py +++ b/src/cpl_query/base/queryable_abc.py @@ -1,10 +1,19 @@ -from abc import abstractmethod, ABC from typing import Optional, Callable, Union +from cpl_query._helper import is_number +from cpl_query.base.sequence_abc import SequenceABC +from cpl_query.exceptions import InvalidTypeException, ArgumentNoneException, ExceptionArgument, IndexOutOfRangeException -class QueryableABC(ABC): - @abstractmethod +def _default_lambda(x: object): + return x + + +class QueryableABC(SequenceABC): + + def __init__(self, t: type = None, values: list = None): + SequenceABC.__init__(self, t, values) + def all(self, _func: Callable = None) -> bool: r"""Checks if every element of list equals result found by function @@ -17,9 +26,11 @@ class QueryableABC(ABC): ------- bool """ - pass + if _func is None: + _func = _default_lambda + + return self.count(_func) == self.count() - @abstractmethod def any(self, _func: Callable = None) -> bool: r"""Checks if list contains result found by function @@ -32,9 +43,11 @@ class QueryableABC(ABC): ------- bool """ - pass + if _func is None: + _func = _default_lambda + + return self.where(_func).count() > 0 - @abstractmethod def average(self, _func: Callable = None) -> Union[int, float, complex]: r"""Returns average value of list @@ -47,10 +60,12 @@ class QueryableABC(ABC): ------- Union[int, float, complex] """ - pass + if _func is None and not is_number(self.type): + raise InvalidTypeException() - @abstractmethod - def contains(self, value: object) -> bool: + return self.sum(_func) / self.count() + + def contains(self, _value: object) -> bool: r"""Checks if list contains value given by function Parameter @@ -62,9 +77,11 @@ class QueryableABC(ABC): ------- bool """ - pass + if _value is None: + raise ArgumentNoneException(ExceptionArgument.value) + + return self.where(lambda x: x == _value).count() > 0 - @abstractmethod def count(self, _func: Callable = None) -> int: r"""Returns length of list or count of found elements @@ -77,9 +94,11 @@ class QueryableABC(ABC): ------- int """ - pass + if _func is None: + return self.__len__() + + return self.where(_func).__len__() - @abstractmethod def distinct(self, _func: Callable = None) -> 'QueryableABC': r"""Returns list without redundancies @@ -92,39 +111,62 @@ class QueryableABC(ABC): ------- :class: `cpl_query.base.queryable_abc.QueryableABC` """ - pass + if _func is None: + _func = _default_lambda - @abstractmethod - def element_at(self, index: int) -> any: + result = [] + known_values = [] + for element in self: + value = _func(element) + if value in known_values: + continue + + known_values.append(value) + result.append(element) + + return type(self)(self._type, result) + + def element_at(self, _index: int) -> any: r"""Returns element at given index Parameter --------- - index: :class:`int` + _index: :class:`int` index Returns ------- - Value at index: any + Value at _index: any """ - pass + if _index is None: + raise ArgumentNoneException(ExceptionArgument.index) - @abstractmethod - def element_at_or_default(self, index: int) -> Optional[any]: + result = self[_index] + if result is None: + raise IndexOutOfRangeException + + return result + + def element_at_or_default(self, _index: int) -> Optional[any]: r"""Returns element at given index or None Parameter --------- - index: :class:`int` + _index: :class:`int` index Returns ------- - Value at index: Optional[any] + Value at _index: Optional[any] """ - pass + if _index is None: + raise ArgumentNoneException(ExceptionArgument.index) + + try: + return self[_index] + except IndexError: + return None - @abstractmethod def first(self) -> any: r"""Returns first element @@ -132,9 +174,11 @@ class QueryableABC(ABC): ------- First element of list: any """ - pass + if len(self) == 0: + raise IndexOutOfRangeException() + + return self[0] - @abstractmethod def first_or_default(self) -> any: r"""Returns first element or None @@ -142,9 +186,11 @@ class QueryableABC(ABC): ------- First element of list: Optional[any] """ - pass + if len(self) == 0: + return None + + return self[0] - @abstractmethod def for_each(self, _func: Callable = None): r"""Runs given function for each element of list @@ -153,7 +199,11 @@ class QueryableABC(ABC): func: :class: `Callable` function to call """ - pass + if _func is not None: + for element in self: + _func(element) + + return self def group_by(self, _func: Callable = None) -> 'QueryableABC': r"""Groups by func @@ -162,9 +212,23 @@ class QueryableABC(ABC): ------- Grouped list[list[any]]: any """ - pass + if _func is None: + _func = _default_lambda + groups = {} + + for v in self: + value = _func(v) + if v not in groups: + groups[value] = [] + + groups[value].append(v) + + v = [] + for g in groups.values(): + v.append(type(self)(None, g)) + x = type(self)(type(self), v) + return x - @abstractmethod def last(self) -> any: r"""Returns last element @@ -172,9 +236,11 @@ class QueryableABC(ABC): ------- Last element of list: any """ - pass + if len(self) == 0: + raise IndexOutOfRangeException() + + return self[len(self) - 1] - @abstractmethod def last_or_default(self) -> any: r"""Returns last element or None @@ -182,9 +248,11 @@ class QueryableABC(ABC): ------- Last element of list: Optional[any] """ - pass + if len(self) == 0: + return None + + return self[len(self) - 1] - @abstractmethod def max(self, _func: Callable = None) -> Union[int, float, complex]: r"""Returns the highest value @@ -197,19 +265,33 @@ class QueryableABC(ABC): ------- Union[int, float, complex] """ - pass + if _func is None and not is_number(self.type): + raise InvalidTypeException() - @abstractmethod - def median(self) -> Union[int, float]: + if _func is None: + _func = _default_lambda + + return _func(max(self, key=_func)) + + def median(self, _func=None) -> Union[int, float]: r"""Return the median value of data elements Returns ------- Union[int, float] """ - pass + if _func is None: + _func = _default_lambda + + result = self.order_by(_func).select(_func).to_list() + length = len(result) + i = int(length / 2) + return ( + result[i] + if length % 2 == 1 + else (float(result[i - 1]) + float(result[i])) / float(2) + ) - @abstractmethod def min(self, _func: Callable = None) -> Union[int, float, complex]: r"""Returns the lowest value @@ -222,9 +304,14 @@ class QueryableABC(ABC): ------- Union[int, float, complex] """ - pass + if _func is None and not is_number(self.type): + raise InvalidTypeException() + + if _func is None: + _func = _default_lambda + + return _func(min(self, key=_func)) - @abstractmethod def order_by(self, _func: Callable = None) -> 'QueryableABC': r"""Sorts elements by function in ascending order @@ -237,9 +324,12 @@ class QueryableABC(ABC): ------- :class: `cpl_query.base.queryable_abc.QueryableABC` """ - pass + if _func is None: + _func = _default_lambda + + from cpl_query.base.ordered_queryable import OrderedQueryable + return OrderedQueryable(self.type, sorted(self, key=_func), _func) - @abstractmethod def order_by_descending(self, _func: Callable = None) -> 'QueryableABC': r"""Sorts elements by function in descending order @@ -252,9 +342,12 @@ class QueryableABC(ABC): ------- :class: `cpl_query.base.queryable_abc.QueryableABC` """ - pass + if _func is None: + _func = _default_lambda + + from cpl_query.base.ordered_queryable import OrderedQueryable + return OrderedQueryable(self.type, sorted(self, key=_func, reverse=True), _func) - @abstractmethod def reverse(self) -> 'QueryableABC': r"""Reverses list @@ -262,29 +355,31 @@ class QueryableABC(ABC): ------- :class: `cpl_query.base.queryable_abc.QueryableABC` """ - pass + return type(self)(self._type, list(reversed(self))) - @abstractmethod - def select(self, _f: Callable) -> 'QueryableABC': + def select(self, _func: Callable) -> 'QueryableABC': r"""Formats each element of list to a given format Returns ------- :class: `cpl_query.base.queryable_abc.QueryableABC` """ - pass + if _func is None: + _func = _default_lambda - @abstractmethod - def select_many(self, _f: Callable) -> 'QueryableABC': + return type(self)(any, [_func(_o) for _o in self]) + + def select_many(self, _func: Callable) -> 'QueryableABC': r"""Flattens resulting lists to one Returns ------- :class: `cpl_query.base.queryable_abc.QueryableABC` """ - pass + # The line below is pain. I don't understand anything of it... + # written on 09.11.2022 by Sven Heidemann + return type(self)(any, [_a for _o in self for _a in _func(_o)]) - @abstractmethod def single(self) -> any: r"""Returns one single element of list @@ -297,9 +392,13 @@ class QueryableABC(ABC): ArgumentNoneException: when argument is None Exception: when argument is None or found more than one element """ - pass + if len(self) > 1: + raise Exception('Found more than one element') + elif len(self) == 0: + raise Exception('Found no element') + + return self[0] - @abstractmethod def single_or_default(self) -> Optional[any]: r"""Returns one single element of list @@ -307,39 +406,48 @@ class QueryableABC(ABC): ------- Found value: Optional[any] """ - pass + if len(self) > 1: + raise Exception('Index out of range') + elif len(self) == 0: + return None - @abstractmethod - def skip(self, index: int) -> 'QueryableABC': + return self[0] + + def skip(self, _index: int) -> 'QueryableABC': r"""Skips all elements from index Parameter --------- - index: :class:`int` + _index: :class:`int` index Returns ------- :class: `cpl_query.base.queryable_abc.QueryableABC` """ - pass + if _index is None: + raise ArgumentNoneException(ExceptionArgument.index) - @abstractmethod - def skip_last(self, index: int) -> 'QueryableABC': + return type(self)(self.type, values=self[_index:]) + + def skip_last(self, _index: int) -> 'QueryableABC': r"""Skips all elements after index Parameter --------- - index: :class:`int` + _index: :class:`int` index Returns ------- :class: `cpl_query.base.queryable_abc.QueryableABC` """ - pass + if _index is None: + raise ArgumentNoneException(ExceptionArgument.index) + + index = len(self) - _index + return type(self)(self._type, self[:index]) - @abstractmethod def sum(self, _func: Callable = None) -> Union[int, float, complex]: r"""Sum of all values @@ -352,39 +460,54 @@ class QueryableABC(ABC): ------- Union[int, float, complex] """ - pass + if _func is None and not is_number(self.type): + raise InvalidTypeException() - @abstractmethod - def take(self, index: int) -> 'QueryableABC': + if _func is None: + _func = _default_lambda + + result = 0 + for x in self: + result += _func(x) + + return result + + def take(self, _index: int) -> 'QueryableABC': r"""Takes all elements from index Parameter --------- - index: :class:`int` + _index: :class:`int` index Returns ------- :class: `cpl_query.base.queryable_abc.QueryableABC` """ - pass + if _index is None: + raise ArgumentNoneException(ExceptionArgument.index) - @abstractmethod - def take_last(self, index: int) -> 'QueryableABC': + return type(self)(self._type, self[:_index]) + + def take_last(self, _index: int) -> 'QueryableABC': r"""Takes all elements after index Parameter --------- - index: :class:`int` + _index: :class:`int` index Returns ------- :class: `cpl_query.base.queryable_abc.QueryableABC` """ - pass + index = len(self) - _index + + if index >= len(self) or index < 0: + raise IndexOutOfRangeException() + + return type(self)(self._type, self[index:]) - @abstractmethod def where(self, _func: Callable = None) -> 'QueryableABC': r"""Select element by function @@ -397,4 +520,15 @@ class QueryableABC(ABC): ------- :class: `cpl_query.base.queryable_abc.QueryableABC` """ - pass + if _func is None: + raise ArgumentNoneException(ExceptionArgument.func) + + if _func is None: + _func = _default_lambda + + result = [] + for element in self: + if _func(element): + result.append(element) + + return type(self)(self.type, result) diff --git a/src/cpl_query/base/sequence_abc.py b/src/cpl_query/base/sequence_abc.py new file mode 100644 index 00000000..8e3a01aa --- /dev/null +++ b/src/cpl_query/base/sequence_abc.py @@ -0,0 +1,86 @@ +from abc import ABC, abstractmethod +from itertools import islice + +from cpl_query.base.sequence_values import SequenceValues + + +class SequenceABC(ABC): + + @abstractmethod + def __init__(self, t: type = None, values: list = None): + ABC.__init__(self) + if values is None: + values = [] + + if t is None and len(values) > 0: + t = type(values[0]) + + if t is None: + t = any + + self._type = t + self._set_values(values) + + def __len__(self): + return len(self._values) + + def __iter__(self): + return iter(self._values) + + def next(self): + return next(self._values) + + def __next__(self): + return self.next() + + def __getitem__(self, n): + values = [x for x in self] + if isinstance(n, slice): + try: + return values[n] + except Exception as e: + raise e + + for i in range(len(values)): + if i == n: + return values[i] + + def __repr__(self): + return f'<{type(self).__name__} {list(self).__repr__()}>' + + @property + def type(self) -> type: + return self._type + + def _check_type(self, __object: any): + if self._type == any: + return + + if self._type is not None and type(__object) != self._type and not isinstance(type(__object), self._type) and not issubclass(type(__object), self._type): + raise Exception(f'Unexpected type: {type(__object)}\nExpected type: {self._type}') + + def _set_values(self, values: list): + self._values = SequenceValues(values, self._type) + + def to_list(self) -> list: + r"""Converts :class: `cpl_query.base.sequence_abc.SequenceABC` to :class: `list` + + Returns + ------- + :class: `list` + """ + return [x for x in self] + + @classmethod + def empty(cls) -> 'SequenceABC': + r"""Returns an empty sequence + + Returns + ------- + Sequence object that contains no elements + """ + return cls() + + @classmethod + def range(cls, start: int, length: int) -> 'SequenceABC': + return cls(int, list(range(start, length))) diff --git a/src/cpl_query/base/sequence_values.py b/src/cpl_query/base/sequence_values.py index 9e2a485e..6298a685 100644 --- a/src/cpl_query/base/sequence_values.py +++ b/src/cpl_query/base/sequence_values.py @@ -5,15 +5,13 @@ from cpl_query.exceptions import IndexOutOfRangeException class SequenceValues: - def __init__(self, data, _t: type): - if data is None: - data = [] - + def __init__(self, data: list, _t: type): if len(data) > 0: def type_check(_t: type, _l: list): - return all(isinstance(x, _t) for x in _l) + return all([_t == any or isinstance(x, _t) for x in _l]) if not type_check(_t, data): + print([type(x) for x in data]) raise Exception(f'Unexpected type\nExpected type: {_t}') if not hasattr(data, '__iter__'): @@ -30,7 +28,7 @@ class SequenceValues: def __iter__(self): i = 0 - while i < len(self): + while i < self._len(): yield next(self._cycle) i += 1 diff --git a/src/cpl_query/enumerable/__init__.py b/src/cpl_query/enumerable/__init__.py index 9bd1147e..f7ff2517 100644 --- a/src/cpl_query/enumerable/__init__.py +++ b/src/cpl_query/enumerable/__init__.py @@ -19,12 +19,9 @@ __version__ = '2022.10.0.post2' from collections import namedtuple - # imports: from .enumerable import Enumerable from .enumerable_abc import EnumerableABC -from .ordered_enumerable import OrderedEnumerable -from .ordered_enumerable_abc import OrderedEnumerableABC VersionInfo = namedtuple('VersionInfo', 'major minor micro') version_info = VersionInfo(major='2022', minor='10', micro='0.post2') diff --git a/src/cpl_query/enumerable/enumerable.py b/src/cpl_query/enumerable/enumerable.py index 81b3647b..7b99518a 100644 --- a/src/cpl_query/enumerable/enumerable.py +++ b/src/cpl_query/enumerable/enumerable.py @@ -1,9 +1,4 @@ -from typing import Union, Callable, Optional - -from cpl_query._helper import is_number from cpl_query.enumerable.enumerable_abc import EnumerableABC -from cpl_query.enumerable.ordered_enumerable_abc import OrderedEnumerableABC -from cpl_query.exceptions import ArgumentNoneException, ExceptionArgument, InvalidTypeException, IndexOutOfRangeException def _default_lambda(x: object): @@ -14,294 +9,5 @@ class Enumerable(EnumerableABC): r"""Implementation of :class: `cpl_query.enumerable.enumerable_abc.EnumerableABC` """ - def __init__(self, t: type = None, values: Union[list, iter] = None): + def __init__(self, t: type = None, values: list = None): EnumerableABC.__init__(self, t, values) - - def all(self, _func: Callable = None) -> bool: - if self is None: - raise ArgumentNoneException(ExceptionArgument.list) - - if _func is None: - _func = _default_lambda - - result = self.where(_func) - return len(result) == len(self) - - def any(self, _func: Callable = None) -> bool: - if self is None: - raise ArgumentNoneException(ExceptionArgument.list) - - if _func is None: - _func = _default_lambda - - result = self.where(_func) - return len(result) > 0 - - def average(self, _func: Callable = None) -> Union[int, float, complex]: - if self is None: - raise ArgumentNoneException(ExceptionArgument.list) - - if _func is None and not is_number(self.type): - raise InvalidTypeException() - - if _func is None: - _func = _default_lambda - - return float(self.sum(_func)) / float(self.count()) - - def contains(self, _value: object) -> bool: - if self is None: - raise ArgumentNoneException(ExceptionArgument.list) - - if _value is None: - raise ArgumentNoneException(ExceptionArgument.value) - - return self.where(lambda x: x == _value).count() > 0 - - def count(self, _func: Callable = None) -> int: - if self is None: - raise ArgumentNoneException(ExceptionArgument.list) - - if _func is None: - return len(self) - - return len(self.where(_func)) - - def distinct(self, _func: Callable = None) -> EnumerableABC: - if self is None: - raise ArgumentNoneException(ExceptionArgument.list) - - if _func is None: - _func = _default_lambda - - result = Enumerable() - known_values = [] - for element in self: - value = _func(element) - if value in known_values: - continue - - known_values.append(value) - result.add(element) - - return result - - def element_at(self, _index: int) -> any: - self._values.reset() - while _index >= 0: - current = self.next() - if _index == 0: - return current - _index -= 1 - - def element_at_or_default(self, _index: int) -> any: - try: - return self.element_at(_index) - except IndexOutOfRangeException: - return None - - @staticmethod - def empty() -> 'EnumerableABC': - r"""Returns an empty enumerable - - Returns - ------- - Enumerable object that contains no elements - """ - return Enumerable() - - def first(self: EnumerableABC, _func=None) -> any: - if _func is not None: - return self.where(_func).element_at(0) - return self.element_at(0) - - def first_or_default(self: EnumerableABC, _func=None) -> Optional[any]: - if _func is not None: - return self.where(_func).element_at_or_default(0) - return self.element_at_or_default(0) - - def for_each(self, _func: Callable = None): - if self is None: - raise ArgumentNoneException(ExceptionArgument.list) - - if _func is None: - _func = _default_lambda - - for element in self: - _func(element) - - def last(self: EnumerableABC) -> any: - return self.element_at(self.count() - 1) - - def last_or_default(self: EnumerableABC) -> Optional[any]: - return self.element_at_or_default(self.count() - 1) - - def max(self, _func: Callable = None) -> Union[int, float, complex]: - if self is None: - raise ArgumentNoneException(ExceptionArgument.list) - - if _func is None and not is_number(self.type): - raise InvalidTypeException() - - if _func is None: - _func = _default_lambda - - return _func(max(self, key=_func)) - - def median(self, _func=None) -> Union[int, float]: - if _func is None: - _func = _default_lambda - result = self.order_by(_func).select(_func).to_list() - length = len(result) - i = int(length / 2) - return ( - result[i] - if length % 2 == 1 - else (float(result[i - 1]) + float(result[i])) / float(2) - ) - - def min(self, _func: Callable = None) -> Union[int, float, complex]: - if self is None: - raise ArgumentNoneException(ExceptionArgument.list) - - if _func is None and not is_number(self.type): - raise InvalidTypeException() - - if _func is None: - _func = _default_lambda - - return _func(min(self, key=_func)) - - def order_by(self, _func: Callable = None) -> OrderedEnumerableABC: - if self is None: - raise ArgumentNoneException(ExceptionArgument.list) - - if _func is None: - _func = _default_lambda - - from cpl_query.enumerable.ordered_enumerable import OrderedEnumerable - return OrderedEnumerable(self.type, _func, sorted(self, key=_func)) - - def order_by_descending(self, _func: Callable = None) -> OrderedEnumerableABC: - if self is None: - raise ArgumentNoneException(ExceptionArgument.list) - - if _func is None: - _func = _default_lambda - - from cpl_query.enumerable.ordered_enumerable import OrderedEnumerable - return OrderedEnumerable(self.type, _func, sorted(self, key=_func, reverse=True)) - - @staticmethod - def range(start: int, length: int) -> 'EnumerableABC': - return Enumerable(int, range(start, length)) - - def reverse(self: EnumerableABC) -> EnumerableABC: - if self is None: - raise ArgumentNoneException(ExceptionArgument.list) - - return Enumerable(self.type, list(reversed(self.to_list()))) - - def select(self, _func: Callable = None) -> EnumerableABC: - if _func is None: - _func = _default_lambda - - _l = [_func(_o) for _o in self] - return Enumerable(self._type if len(_l) < 1 else type(_l[0]), _l) - - def select_many(self, _func: Callable = None) -> EnumerableABC: - if _func is None: - _func = _default_lambda - - # The line below is pain. I don't understand anything of the list comprehension... - # written on 09.11.2022 by Sven Heidemann - _l = [_a for _o in self for _a in _func(_o)] - return Enumerable(self._type if len(_l) < 1 else type(_l[0]), _l) - - def single(self: EnumerableABC) -> any: - if self is None: - raise ArgumentNoneException(ExceptionArgument.list) - - if len(self) > 1: - raise IndexError('Found more than one element') - elif len(self) == 0: - raise IndexOutOfRangeException(f'{type(self).__name__} is empty') - - return self.element_at(0) - - def single_or_default(self: EnumerableABC) -> Optional[any]: - if self is None: - raise ArgumentNoneException(ExceptionArgument.list) - - if len(self) > 1: - raise IndexError('Found more than one element') - elif len(self) == 0: - return None - - return self.element_at(0) - - def skip(self, _index: int) -> EnumerableABC: - if self is None: - raise ArgumentNoneException(ExceptionArgument.list) - - if _index is None: - raise ArgumentNoneException(ExceptionArgument.index) - - _list = self.to_list() - - return Enumerable(self.type, _list[_index:]) - - def skip_last(self, _index: int) -> EnumerableABC: - if self is None: - raise ArgumentNoneException(ExceptionArgument.list) - - if _index is None: - raise ArgumentNoneException(ExceptionArgument.index) - - index = len(self) - _index - - return self.take(len(self) - _index) - - def take(self, _index: int) -> EnumerableABC: - if self is None: - raise ArgumentNoneException(ExceptionArgument.list) - - if _index is None: - raise ArgumentNoneException(ExceptionArgument.index) - - _list = self.to_list() - - return Enumerable(self.type, _list[:_index]) - - def take_last(self, _index: int) -> EnumerableABC: - if self is None: - raise ArgumentNoneException(ExceptionArgument.list) - - if _index is None: - raise ArgumentNoneException(ExceptionArgument.index) - - _list = self.to_list() - index = len(_list) - _index - - return self.skip(index) - - def sum(self, _func: Callable = None) -> Union[int, float, complex]: - if self is None: - raise ArgumentNoneException(ExceptionArgument.list) - - if _func is None and not is_number(self.type): - raise InvalidTypeException() - - if _func is None: - _func = _default_lambda - - return sum([_func(x) for x in self]) - - def where(self, _func: Callable = None) -> EnumerableABC: - if self is None: - raise ArgumentNoneException(ExceptionArgument.list) - - if _func is None: - raise ArgumentNoneException(ExceptionArgument.func) - - return Enumerable(self.type, list(filter(_func, self._values))) diff --git a/src/cpl_query/enumerable/enumerable_abc.py b/src/cpl_query/enumerable/enumerable_abc.py index ff587f1a..b59054d9 100644 --- a/src/cpl_query/enumerable/enumerable_abc.py +++ b/src/cpl_query/enumerable/enumerable_abc.py @@ -1,8 +1,6 @@ from abc import abstractmethod -from typing import Iterable from cpl_query.base.queryable_abc import QueryableABC -from cpl_query.base.sequence_values import SequenceValues class EnumerableABC(QueryableABC): @@ -11,81 +9,15 @@ class EnumerableABC(QueryableABC): @abstractmethod def __init__(self, t: type = None, values: list = None): - if t == any or t is None and values is not None: - t = type(values[0]) + QueryableABC.__init__(self, t, values) - self._type, self._values, self._remove_error_check = t, SequenceValues(values, t), True - - def __len__(self): - return len(self._values) - - def __iter__(self): - return iter(self._values) - - def next(self): - return next(self._values) - - def __next__(self): - return self.next() - - def __repr__(self): - return f'<{type(self).__name__} {list(self).__repr__()}>' - - @property - def type(self) -> type: - return self._type + self._remove_error_check = True def set_remove_error_check(self, _value: bool): r"""Set flag to check if element exists before removing """ self._remove_error_check = _value - def add(self, __object: object) -> None: - r"""Adds an element to the enumerable. - """ - if self._type is not None and type(__object) != self._type and not isinstance(type(__object), self._type) and not issubclass(type(__object), self._type): - raise Exception(f'Unexpected type: {type(__object)}\nExpected type: {self._type}') - - if len(self) == 0 and self._type is None: - self._type = type(__object) - - self._values = SequenceValues([*self._values, __object], self._type) - - def clear(self): - r"""Removes all elements - """ - del self._values - self._values = [] - - def extend(self, __list: Iterable) -> 'EnumerableABC': - r"""Adds elements of given list to enumerable - - Parameter - --------- - __enumerable: :class: `cpl_query.enumerable.enumerable_abc.EnumerableABC` - index - """ - self._values = SequenceValues([*self._values, *__list], self._type) - return self - - def remove(self, __object: object) -> None: - r"""Removes element from list - - Parameter - --------- - __object: :class:`object` - value - - Raises - --------- - `Element not found` when element does not exist. Check can be deactivated by calling .set_remove_error_check(False) - """ - if self._remove_error_check and __object not in self._values: - raise Exception('Element not found') - - # self._values.remove(__object) - self._values = SequenceValues([x for x in self.to_list() if x != __object], self._type) - def to_iterable(self) -> 'IterableABC': r"""Converts :class: `cpl_query.enumerable.enumerable_abc.EnumerableABC` to :class: `cpl_query.iterable.iterable_abc.IterableABC` @@ -95,12 +27,3 @@ class EnumerableABC(QueryableABC): """ from cpl_query.iterable.iterable import Iterable return Iterable(self._type, self.to_list()) - - def to_list(self) -> list: - r"""Converts :class: `cpl_query.base.sequence_abc.SequenceABC` to :class: `list` - - Returns - ------- - :class: `list` - """ - return [x for x in self] diff --git a/src/cpl_query/enumerable/ordered_enumerable.py b/src/cpl_query/enumerable/ordered_enumerable.py deleted file mode 100644 index 28628da0..00000000 --- a/src/cpl_query/enumerable/ordered_enumerable.py +++ /dev/null @@ -1,36 +0,0 @@ -from collections.abc import Callable -from typing import Iterable - -from cpl_query.enumerable.enumerable import Enumerable -from cpl_query.enumerable.ordered_enumerable_abc import OrderedEnumerableABC -from cpl_query.exceptions import ArgumentNoneException, ExceptionArgument - - -class OrderedEnumerable(Enumerable, OrderedEnumerableABC): - r"""Implementation of :class: `cpl_query.extension.Enumerable` `cpl_query.extension.OrderedEnumerableABC` - """ - - def __init__(self, _t: type, _func: Callable = None, _values: Iterable = None): - Enumerable.__init__(self, _t) - OrderedEnumerableABC.__init__(self, _t, _func, _values) - - def then_by(self: OrderedEnumerableABC, _func: Callable) -> OrderedEnumerableABC: - if self is None: - raise ArgumentNoneException(ExceptionArgument.list) - - if _func is None: - raise ArgumentNoneException(ExceptionArgument.func) - - self._funcs.append(_func) - - return OrderedEnumerable(self.type, _func, sorted(self, key=lambda *args: [f(*args) for f in self._funcs])) - - def then_by_descending(self: OrderedEnumerableABC, _func: Callable) -> OrderedEnumerableABC: - if self is None: - raise ArgumentNoneException(ExceptionArgument.list) - - if _func is None: - raise ArgumentNoneException(ExceptionArgument.func) - - self._funcs.append(_func) - return OrderedEnumerable(self.type, _func, sorted(self, key=lambda *args: [f(*args) for f in self._funcs], reverse=True)) diff --git a/src/cpl_query/enumerable/ordered_enumerable_abc.py b/src/cpl_query/enumerable/ordered_enumerable_abc.py deleted file mode 100644 index ef83f07b..00000000 --- a/src/cpl_query/enumerable/ordered_enumerable_abc.py +++ /dev/null @@ -1,43 +0,0 @@ -from abc import abstractmethod -from collections.abc import Callable -from typing import Iterable - -from cpl_query.enumerable.enumerable_abc import EnumerableABC - - -class OrderedEnumerableABC(EnumerableABC): - - @abstractmethod - def __init__(self, _t: type, _func: Callable = None, _values: Iterable = None): - EnumerableABC.__init__(self, _t, _values) - self._funcs: list[Callable] = [] - if _func is not None: - self._funcs.append(_func) - - @abstractmethod - def then_by(self, func: Callable) -> 'OrderedEnumerableABC': - r"""Sorts OrderedList in ascending order by function - - Parameter - --------- - func: :class:`Callable` - - Returns - ------- - list of :class:`cpl_query.extension.OrderedEnumerableABC` - """ - pass - - @abstractmethod - def then_by_descending(self, func: Callable) -> 'OrderedEnumerableABC': - r"""Sorts OrderedList in descending order by function - - Parameter - --------- - func: :class:`Callable` - - Returns - ------- - list of :class:`cpl_query.extension.OrderedEnumerableABC` - """ - pass diff --git a/src/cpl_query/iterable/__init__.py b/src/cpl_query/iterable/__init__.py index d129a1c1..2f6ae095 100644 --- a/src/cpl_query/iterable/__init__.py +++ b/src/cpl_query/iterable/__init__.py @@ -23,8 +23,6 @@ from collections import namedtuple # imports: from .iterable_abc import IterableABC from .iterable import Iterable -from .ordered_iterable_abc import OrderedIterableABC -from .ordered_iterable import OrderedIterable VersionInfo = namedtuple('VersionInfo', 'major minor micro') version_info = VersionInfo(major='2022', minor='10', micro='0.post2') diff --git a/src/cpl_query/iterable/iterable.py b/src/cpl_query/iterable/iterable.py index 6f7ce73f..38367269 100644 --- a/src/cpl_query/iterable/iterable.py +++ b/src/cpl_query/iterable/iterable.py @@ -1,9 +1,6 @@ -from typing import Callable, Optional, Union, Iterable as IterableType +from typing import Iterable as IterableType -from cpl_query._helper import is_number -from cpl_query.exceptions import ArgumentNoneException, ExceptionArgument, InvalidTypeException, IndexOutOfRangeException from cpl_query.iterable.iterable_abc import IterableABC -from cpl_query.iterable.ordered_iterable_abc import OrderedIterableABC def _default_lambda(x: object): @@ -14,251 +11,3 @@ class Iterable(IterableABC): def __init__(self, t: type = None, values: IterableType = None): IterableABC.__init__(self, t, values) - - def all(self, _func: Callable = None) -> bool: - if _func is None: - _func = _default_lambda - - return self.where(_func).count() == self.count() - - def any(self, _func: Callable = None) -> bool: - if _func is None: - _func = _default_lambda - - return self.where(_func).count() > 0 - - def average(self, _func: Callable = None) -> Union[int, float, complex]: - if _func is None and not is_number(self.type): - raise InvalidTypeException() - - return self.sum(_func) / self.count() - - def contains(self, _value: object) -> bool: - if _value is None: - raise ArgumentNoneException(ExceptionArgument.value) - - return self.where(lambda x: x == _value).count() > 0 - - def count(self, _func: Callable = None) -> int: - if _func is None: - return self.__len__() - - return self.where(_func).__len__() - - def distinct(self, _func: Callable = None) -> 'Iterable': - if _func is None: - _func = _default_lambda - - result = Iterable() - known_values = [] - for element in self: - value = _func(element) - if value in known_values: - continue - - known_values.append(value) - result.append(element) - - return result - - def element_at(self, _index: int) -> any: - if _index is None: - raise ArgumentNoneException(ExceptionArgument.index) - - return self[_index] - - def element_at_or_default(self, _index: int) -> any: - if _index is None: - raise ArgumentNoneException(ExceptionArgument.index) - - try: - return self[_index] - except IndexError: - return None - - def first(self) -> any: - if len(self) == 0: - raise IndexOutOfRangeException() - - return self[0] - - def first_or_default(self) -> Optional[any]: - if len(self) == 0: - return None - - return self[0] - - def group_by(self, _func: Callable = None) -> 'Iterable': - if _func is None: - _func = _default_lambda - groups = {} - - for v in self: - value = _func(v) - if v not in groups: - groups[value] = Iterable(type(v)) - - groups[value].append(v) - - return Iterable(Iterable).extend(groups.values()) - - def last(self) -> any: - if len(self) == 0: - raise IndexOutOfRangeException() - - return self[len(self) - 1] - - def last_or_default(self) -> Optional[any]: - if len(self) == 0: - return None - - return self[len(self) - 1] - - def for_each(self, _func: Callable = None) -> 'Iterable': - if _func is not None: - for element in self: - _func(element) - - return self - - def max(self, _func: Callable = None) -> Union[int, float, complex]: - if _func is None and not is_number(self.type): - raise InvalidTypeException() - - if _func is None: - _func = _default_lambda - - return _func(max(self, key=_func)) - - def median(self, _func=None) -> Union[int, float]: - if _func is None: - _func = _default_lambda - - result = self.order_by(_func).select(_func).to_list() - length = len(result) - i = int(length / 2) - return ( - result[i] - if length % 2 == 1 - else (float(result[i - 1]) + float(result[i])) / float(2) - ) - - def min(self, _func: Callable = None) -> Union[int, float, complex]: - if _func is None and not is_number(self.type): - raise InvalidTypeException() - - if _func is None: - _func = _default_lambda - - return _func(min(self, key=_func)) - - def order_by(self, _func: Callable = None) -> OrderedIterableABC: - if _func is None: - _func = _default_lambda - - from cpl_query.iterable.ordered_iterable import OrderedIterable - return OrderedIterable(self.type, _func, sorted(self, key=_func)) - - def order_by_descending(self, _func: Callable = None) -> OrderedIterableABC: - if _func is None: - _func = _default_lambda - - from cpl_query.iterable.ordered_iterable import OrderedIterable - return OrderedIterable(self.type, _func, sorted(self, key=_func, reverse=True)) - - def reverse(self) -> 'Iterable': - return Iterable().extend(reversed(self)) - - def select(self, _func: Callable = None) -> 'Iterable': - if _func is None: - _func = _default_lambda - - result = Iterable() - result.extend(_func(_o) for _o in self) - return result - - def select_many(self, _func: Callable = None) -> 'Iterable': - result = Iterable() - # The line below is pain. I don't understand anything of it... - # written on 09.11.2022 by Sven Heidemann - elements = [_a for _o in self for _a in _func(_o)] - - result.extend(elements) - return result - - def single(self) -> any: - if len(self) > 1: - raise Exception('Found more than one element') - elif len(self) == 0: - raise Exception('Found no element') - - return self[0] - - def single_or_default(self) -> Optional[any]: - if len(self) > 1: - raise Exception('Index out of range') - elif len(self) == 0: - return None - - return self[0] - - def skip(self, _index: int) -> 'Iterable': - if _index is None: - raise ArgumentNoneException(ExceptionArgument.index) - - return Iterable(self.type, values=self[_index:]) - - def skip_last(self, _index: int) -> 'Iterable': - if _index is None: - raise ArgumentNoneException(ExceptionArgument.index) - - index = len(self) - _index - - result = Iterable() - result.extend(self[:index]) - return result - - def sum(self, _func: Callable = None) -> Union[int, float, complex]: - if _func is None and not is_number(self.type): - raise InvalidTypeException() - - if _func is None: - _func = _default_lambda - - result = 0 - for x in self: - result += _func(x) - - return result - - def take(self, _index: int) -> 'Iterable': - if _index is None: - raise ArgumentNoneException(ExceptionArgument.index) - - result = Iterable() - result.extend(self[:_index]) - return result - - def take_last(self, _index: int) -> 'Iterable': - index = len(self) - _index - - if index >= len(self) or index < 0: - raise IndexOutOfRangeException() - - result = Iterable() - result.extend(self[index:]) - return result - - def where(self, _func: Callable = None) -> 'Iterable': - if _func is None: - raise ArgumentNoneException(ExceptionArgument.func) - - if _func is None: - _func = _default_lambda - - result = Iterable(self.type) - for element in self: - if _func(element): - result.append(element) - - return result diff --git a/src/cpl_query/iterable/iterable_abc.py b/src/cpl_query/iterable/iterable_abc.py index 5c6c47db..dcae50b3 100644 --- a/src/cpl_query/iterable/iterable_abc.py +++ b/src/cpl_query/iterable/iterable_abc.py @@ -4,54 +4,45 @@ from typing import Iterable from cpl_query.base.queryable_abc import QueryableABC -class IterableABC(list, QueryableABC): +class IterableABC(QueryableABC): r"""ABC to define functions on list """ @abstractmethod def __init__(self, t: type = None, values: Iterable = None): - values = [] if values is None else values - list.__init__(self, values) + QueryableABC.__init__(self, t, values) - if t is None and len(values) > 0: - t = type(values[0]) + def __setitem__(self, i, val): + self._check_type(val) + values = [*self._values] + values[i] = val + self._set_values(values) - self._type = t - - def __repr__(self): - return f'<{type(self).__name__} {list(self).__repr__()}>' + def __delitem__(self, i): + values = [*self._values] + del values[i] + self._set_values(values) @property def type(self) -> type: return self._type - def to_list(self) -> list: - r"""Converts :class: `cpl_query.base.sequence_abc.SequenceABC` to :class: `list` - - Returns - ------- - :class: `list` - """ - return [x for x in self] - def __str__(self): return str(self.to_list()) - def append(self, __object: object) -> None: + def append(self, _object: object): + self.add(_object) + + def add(self, _object: object): r"""Adds element to list Parameter --------- - __object: :class:`object` + _object: :class:`object` value """ - if self._type is not None and type(__object) != self._type and not isinstance(type(__object), self._type) and not issubclass(type(__object), self._type): - raise Exception(f'Unexpected type: {type(__object)}\nExpected type: {self._type}') - - if len(self) == 0 and self._type is None: - self._type = type(__object) - - # self._values = SequenceValues([*self._values, __object], self._type) - super().append(__object) + self._check_type(_object) + values = [*self._values, _object] + self._set_values(values) def extend(self, __iterable: Iterable) -> 'IterableABC': r"""Adds elements of given list to list diff --git a/src/cpl_query/iterable/ordered_iterable.py b/src/cpl_query/iterable/ordered_iterable.py deleted file mode 100644 index 9fe50079..00000000 --- a/src/cpl_query/iterable/ordered_iterable.py +++ /dev/null @@ -1,35 +0,0 @@ -from collections.abc import Callable - -from cpl_query.exceptions import ArgumentNoneException, ExceptionArgument -from cpl_query.iterable.iterable import Iterable -from cpl_query.iterable.ordered_iterable_abc import OrderedIterableABC - - -class OrderedIterable(Iterable, OrderedIterableABC): - r"""Implementation of :class: `cpl_query.extension.Iterable` `cpl_query.extension.OrderedIterableABC` - """ - - def __init__(self, _t: type, _func: Callable = None, _values: Iterable = None): - Iterable.__init__(self, _t) - OrderedIterableABC.__init__(self, _t, _func, _values) - - def then_by(self: OrderedIterableABC, _func: Callable) -> OrderedIterableABC: - if self is None: - raise ArgumentNoneException(ExceptionArgument.list) - - if _func is None: - raise ArgumentNoneException(ExceptionArgument.func) - - self._funcs.append(_func) - - return OrderedIterable(self.type, _func, sorted(self, key=lambda *args: [f(*args) for f in self._funcs])) - - def then_by_descending(self: OrderedIterableABC, _func: Callable) -> OrderedIterableABC: - if self is None: - raise ArgumentNoneException(ExceptionArgument.list) - - if _func is None: - raise ArgumentNoneException(ExceptionArgument.func) - - self._funcs.append(_func) - return OrderedIterable(self.type, _func, sorted(self, key=lambda *args: [f(*args) for f in self._funcs], reverse=True)) diff --git a/unittests/unittests_cli/constants.py b/unittests/unittests_cli/constants.py index c8318dab..13986be9 100644 --- a/unittests/unittests_cli/constants.py +++ b/unittests/unittests_cli/constants.py @@ -6,4 +6,4 @@ if not os.getcwd().endswith('unittests'): PLAYGROUND_PATH = os.path.abspath(os.path.join(os.getcwd(), f'{base}test_cli_playground')) TRANSLATION_PATH = os.path.abspath(os.path.join(os.getcwd(), f'{base}unittests_translation')) -CLI_PATH = os.path.abspath(os.path.join(os.getcwd(), f'{base}../src/cpl_cli/main.py')) +CLI_PATH = os.path.abspath(os.path.join(os.getcwd(), f'../../src/cpl_cli/main.py')) diff --git a/unittests/unittests_query/enumerable_query_test_case.py b/unittests/unittests_query/enumerable_query_test_case.py index d95275e0..ffe71576 100644 --- a/unittests/unittests_query/enumerable_query_test_case.py +++ b/unittests/unittests_query/enumerable_query_test_case.py @@ -11,7 +11,16 @@ from unittests_query.models import User, Address class EnumerableQueryTestCase(unittest.TestCase): def setUp(self) -> None: - self._tests = Enumerable(User) + users = [] + for i in range(0, 100): + users.append(User( + String.random_string(string.ascii_letters, 8).lower(), + Address( + String.random_string(string.ascii_letters, 10).lower(), + randint(1, 10) + ) + )) + self._t_user = User( 'Test user', Address( @@ -27,22 +36,10 @@ class EnumerableQueryTestCase(unittest.TestCase): ) ) - self._generate_test_data() + users.append(self._t_user) + users.append(self._t_user2) - def _generate_test_data(self): - for i in range(0, 100): - user = User( - String.random_string(string.ascii_letters, 8).lower(), - Address( - String.random_string(string.ascii_letters, 10).lower(), - randint(1, 10) - ) - ) - - self._tests.add(user) - - self._tests.add(self._t_user) - self._tests.add(self._t_user2) + self._tests = Enumerable(User, users) def test_any(self): results = [] diff --git a/unittests/unittests_query/enumerable_test_case.py b/unittests/unittests_query/enumerable_test_case.py index 60fe056e..b8605493 100644 --- a/unittests/unittests_query/enumerable_test_case.py +++ b/unittests/unittests_query/enumerable_test_case.py @@ -6,17 +6,9 @@ from cpl_query.enumerable.enumerable import Enumerable class EnumerableTestCase(unittest.TestCase): def setUp(self) -> None: - self._list = Enumerable(int) - - def _clear(self): - self._list.clear() - self.assertEqual(self._list, []) + self._list = Enumerable(int, list(range(1, 4))) def test_append(self): - self._list.add(1) - self._list.add(2) - self._list.add(3) - self.assertEqual(self._list.to_list(), [1, 2, 3]) self.assertRaises(Exception, lambda v: self._list.add(v), '3') @@ -38,30 +30,7 @@ class EnumerableTestCase(unittest.TestCase): n += 1 def test_get(self): - self._list.add(1) - self._list.add(2) - self._list.add(3) - self.assertEqual(self._list.element_at(2), [1, 2, 3][2]) def test_count(self): - self._list.add(1) - self._list.add(2) - self._list.add(3) - self.assertEqual(self._list.count(), 3) - - def test_remove(self): - old_values = self._list._values - self._list.add(1) - self.assertNotEqual(old_values, self._list._values) - self._list.add(2) - self._list.add(3) - - self.assertEqual(self._list.to_list(), [1, 2, 3]) - self.assertRaises(Exception, lambda v: self._list.add(v), '3') - old_values = self._list._values - self._list.remove(3) - self.assertNotEqual(old_values, self._list._values) - self.assertEqual(self._list.to_list(), [1, 2]) - self.assertRaises(Exception, lambda v: self._list.add(v), '3') diff --git a/unittests/unittests_query/iterable_query_test_case.py b/unittests/unittests_query/iterable_query_test_case.py index c48fc9ea..8ea3a0b8 100644 --- a/unittests/unittests_query/iterable_query_test_case.py +++ b/unittests/unittests_query/iterable_query_test_case.py @@ -113,7 +113,7 @@ class IterableQueryTestCase(unittest.TestCase): addresses.append(u.address.nr) - res2 = self._tests.distinct(lambda x: x.address.nr).select(lambda x: x.address.nr) + res2 = self._tests.distinct(lambda x: x.address.nr).select(lambda x: x.address.nr).to_list() self.assertEqual(addresses, res2) def test_element_at(self): @@ -199,7 +199,7 @@ class IterableQueryTestCase(unittest.TestCase): groups[v].append(v) elements.append(v) - r1, r2 = list(groups.values()), elements.group_by() + r1, r2 = list(groups.values()), elements.group_by().select(lambda l: l.to_list()).to_list() self.assertEqual(r1, r2) def test_for_each(self): diff --git a/unittests/unittests_query/models.py b/unittests/unittests_query/models.py index d3b147dd..54c9522c 100644 --- a/unittests/unittests_query/models.py +++ b/unittests/unittests_query/models.py @@ -4,9 +4,15 @@ class User: self.name = name self.address = address + def __repr__(self): + return f'<{type(self).__name__} {self.name} {self.address}>' + class Address: def __init__(self, street, nr): self.street = street - self.nr = nr \ No newline at end of file + self.nr = nr + + def __repr__(self): + return f'<{type(self).__name__} {self.street} {self.nr}>' -- 2.45.2 From 2840628443584c18a2a016ac3419612c142c2e36 Mon Sep 17 00:00:00 2001 From: Sven Heidemann Date: Thu, 1 Dec 2022 23:23:48 +0100 Subject: [PATCH 4/6] Improved tests #129 --- unittests/unittests_cli/abc/__init__.py | 0 .../unittests_cli/abc/command_test_case.py | 35 +++++++++++++++++++ unittests/unittests_cli/add_test_case.py | 20 +++-------- unittests/unittests_cli/build_test_case.py | 16 +++------ unittests/unittests_cli/cli_test_suite.py | 5 +++ unittests/unittests_cli/constants.py | 2 +- unittests/unittests_cli/custom_test_case.py | 4 +-- unittests/unittests_cli/generate_test_case.py | 5 +-- unittests/unittests_cli/install_test_case.py | 14 +++----- unittests/unittests_cli/new_test_case.py | 14 +++----- unittests/unittests_cli/publish_test_case.py | 14 +++----- unittests/unittests_cli/remove_test_case.py | 7 ++-- unittests/unittests_cli/run_test_case.py | 19 +++------- unittests/unittests_cli/start_test_case.py | 14 +++----- .../unittests_cli/uninstall_test_case.py | 16 +++------ unittests/unittests_cli/update_test_case.py | 34 +++++++----------- unittests/unittests_cli/version_test_case.py | 7 ++-- unittests/unittests_shared/cli_commands.py | 4 ++- .../translation_test_suite.py | 26 -------------- 19 files changed, 103 insertions(+), 153 deletions(-) create mode 100644 unittests/unittests_cli/abc/__init__.py create mode 100644 unittests/unittests_cli/abc/command_test_case.py diff --git a/unittests/unittests_cli/abc/__init__.py b/unittests/unittests_cli/abc/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/unittests/unittests_cli/abc/command_test_case.py b/unittests/unittests_cli/abc/command_test_case.py new file mode 100644 index 00000000..3b609cab --- /dev/null +++ b/unittests/unittests_cli/abc/command_test_case.py @@ -0,0 +1,35 @@ +import os +import shutil +import traceback +import unittest + +from unittests_cli.constants import PLAYGROUND_PATH + + +class CommandTestCase(unittest.TestCase): + + def __init__(self, method_name: str): + unittest.TestCase.__init__(self, method_name) + + @classmethod + def setUpClass(cls): + + try: + if os.path.exists(PLAYGROUND_PATH): + shutil.rmtree(os.path.abspath(os.path.join(PLAYGROUND_PATH))) + + os.makedirs(PLAYGROUND_PATH) + os.chdir(PLAYGROUND_PATH) + except Exception as e: + print(f'Setup of {__name__} failed: {traceback.format_exc()}') + + def setUp(self): + os.chdir(PLAYGROUND_PATH) + + @classmethod + def tearDownClass(cls): + try: + if os.path.exists(PLAYGROUND_PATH): + shutil.rmtree(os.path.abspath(os.path.join(PLAYGROUND_PATH))) + except Exception as e: + print(f'Cleanup of {__name__} failed: {traceback.format_exc()}') diff --git a/unittests/unittests_cli/add_test_case.py b/unittests/unittests_cli/add_test_case.py index d1b64503..18efb1bd 100644 --- a/unittests/unittests_cli/add_test_case.py +++ b/unittests/unittests_cli/add_test_case.py @@ -1,18 +1,16 @@ import json import os -import shutil -import unittest from cpl_core.utils import String - +from unittests_cli.abc.command_test_case import CommandTestCase from unittests_cli.constants import PLAYGROUND_PATH from unittests_shared.cli_commands import CLICommands -class AddTestCase(unittest.TestCase): +class AddTestCase(CommandTestCase): - def __init__(self, methodName: str): - unittest.TestCase.__init__(self, methodName) + def __init__(self, method_name: str): + CommandTestCase.__init__(self, method_name) self._source = 'add-test-project' self._target = 'add-test-library' self._project_file = f'src/{String.convert_to_snake_case(self._source)}/{self._source}.json' @@ -26,22 +24,12 @@ class AddTestCase(unittest.TestCase): return project_json def setUp(self): - if not os.path.exists(PLAYGROUND_PATH): - os.makedirs(PLAYGROUND_PATH) - os.chdir(PLAYGROUND_PATH) # create projects CLICommands.new('console', self._source, '--ab', '--s') os.chdir(os.path.join(os.getcwd(), self._source)) CLICommands.new('console', self._target, '--ab', '--s') - def cleanUp(self): - # remove projects - if not os.path.exists(os.path.abspath(os.path.join(PLAYGROUND_PATH, self._source))): - return - - shutil.rmtree(os.path.abspath(os.path.join(PLAYGROUND_PATH, self._source))) - def test_add(self): CLICommands.add(self._source, self._target) settings = self._get_project_settings() diff --git a/unittests/unittests_cli/build_test_case.py b/unittests/unittests_cli/build_test_case.py index d01de06f..a0117712 100644 --- a/unittests/unittests_cli/build_test_case.py +++ b/unittests/unittests_cli/build_test_case.py @@ -2,18 +2,17 @@ import filecmp import json import os import shutil -import unittest from cpl_core.utils import String - +from unittests_cli.abc.command_test_case import CommandTestCase from unittests_cli.constants import PLAYGROUND_PATH from unittests_shared.cli_commands import CLICommands -class BuildTestCase(unittest.TestCase): +class BuildTestCase(CommandTestCase): - def __init__(self, methodName: str): - unittest.TestCase.__init__(self, methodName) + def __init__(self, method_name: str): + CommandTestCase.__init__(self, method_name) self._source = 'build-test-source' self._project_file = f'src/{String.convert_to_snake_case(self._source)}/{self._source}.json' @@ -39,13 +38,6 @@ class BuildTestCase(unittest.TestCase): CLICommands.new('console', self._source, '--ab', '--s') os.chdir(os.path.join(os.getcwd(), self._source)) - def cleanUp(self): - # remove projects - if not os.path.exists(os.path.abspath(os.path.join(PLAYGROUND_PATH, self._source))): - return - - shutil.rmtree(os.path.abspath(os.path.join(PLAYGROUND_PATH, self._source))) - def _are_dir_trees_equal(self, dir1, dir2): """ found at https://stackoverflow.com/questions/4187564/recursively-compare-two-directories-to-ensure-they-have-the-same-files-and-subdi diff --git a/unittests/unittests_cli/cli_test_suite.py b/unittests/unittests_cli/cli_test_suite.py index 228fa366..9ca8b469 100644 --- a/unittests/unittests_cli/cli_test_suite.py +++ b/unittests/unittests_cli/cli_test_suite.py @@ -76,3 +76,8 @@ class CLITestSuite(unittest.TestSuite): self._setup() self._result = super().run(*args) self._cleanup() + + +if __name__ == "__main__": + runner = unittest.TextTestRunner() + runner.run(CLITestSuite()) diff --git a/unittests/unittests_cli/constants.py b/unittests/unittests_cli/constants.py index 13986be9..c8318dab 100644 --- a/unittests/unittests_cli/constants.py +++ b/unittests/unittests_cli/constants.py @@ -6,4 +6,4 @@ if not os.getcwd().endswith('unittests'): PLAYGROUND_PATH = os.path.abspath(os.path.join(os.getcwd(), f'{base}test_cli_playground')) TRANSLATION_PATH = os.path.abspath(os.path.join(os.getcwd(), f'{base}unittests_translation')) -CLI_PATH = os.path.abspath(os.path.join(os.getcwd(), f'../../src/cpl_cli/main.py')) +CLI_PATH = os.path.abspath(os.path.join(os.getcwd(), f'{base}../src/cpl_cli/main.py')) diff --git a/unittests/unittests_cli/custom_test_case.py b/unittests/unittests_cli/custom_test_case.py index db59be7d..9088cc44 100644 --- a/unittests/unittests_cli/custom_test_case.py +++ b/unittests/unittests_cli/custom_test_case.py @@ -1,7 +1,7 @@ -import unittest +from unittests_cli.abc.command_test_case import CommandTestCase -class CustomTestCase(unittest.TestCase): +class CustomTestCase(CommandTestCase): def setUp(self): pass diff --git a/unittests/unittests_cli/generate_test_case.py b/unittests/unittests_cli/generate_test_case.py index c53f28d5..cba7d8a7 100644 --- a/unittests/unittests_cli/generate_test_case.py +++ b/unittests/unittests_cli/generate_test_case.py @@ -1,17 +1,18 @@ import os.path -import unittest from cpl_core.utils import String +from unittests_cli.abc.command_test_case import CommandTestCase from unittests_cli.constants import PLAYGROUND_PATH from unittests_shared.cli_commands import CLICommands -class GenerateTestCase(unittest.TestCase): +class GenerateTestCase(CommandTestCase): _project = 'test-console' _t_path = 'test' @classmethod def setUpClass(cls): + CommandTestCase.setUpClass() CLICommands.new('console', cls._project, '--ab', '--s', '--venv') def setUp(self): diff --git a/unittests/unittests_cli/install_test_case.py b/unittests/unittests_cli/install_test_case.py index 516a44e4..eaa2a367 100644 --- a/unittests/unittests_cli/install_test_case.py +++ b/unittests/unittests_cli/install_test_case.py @@ -6,14 +6,15 @@ import sys import unittest from cpl_core.utils import String +from unittests_cli.abc.command_test_case import CommandTestCase from unittests_cli.constants import PLAYGROUND_PATH from unittests_shared.cli_commands import CLICommands -class InstallTestCase(unittest.TestCase): +class InstallTestCase(CommandTestCase): - def __init__(self, methodName: str): - unittest.TestCase.__init__(self, methodName) + def __init__(self, method_name: str): + CommandTestCase.__init__(self, method_name) self._source = 'install-test-source' self._project_file = f'src/{String.convert_to_snake_case(self._source)}/{self._source}.json' @@ -39,13 +40,6 @@ class InstallTestCase(unittest.TestCase): CLICommands.new('console', self._source, '--ab', '--s') os.chdir(os.path.join(os.getcwd(), self._source)) - def cleanUp(self): - # remove projects - if not os.path.exists(os.path.abspath(os.path.join(PLAYGROUND_PATH, self._source))): - return - - shutil.rmtree(os.path.abspath(os.path.join(PLAYGROUND_PATH, self._source))) - def _get_installed_packages(self) -> dict: reqs = subprocess.check_output([sys.executable, '-m', 'pip', 'freeze']) return dict([tuple(r.decode().split('==')) for r in reqs.split()]) diff --git a/unittests/unittests_cli/new_test_case.py b/unittests/unittests_cli/new_test_case.py index 5d0eb0a3..21191333 100644 --- a/unittests/unittests_cli/new_test_case.py +++ b/unittests/unittests_cli/new_test_case.py @@ -1,19 +1,16 @@ import json import os -import unittest from cpl_core.utils import String +from unittests_cli.abc.command_test_case import CommandTestCase from unittests_cli.constants import PLAYGROUND_PATH from unittests_shared.cli_commands import CLICommands -class NewTestCase(unittest.TestCase): +class NewTestCase(CommandTestCase): - def setUp(self): - if not os.path.exists(PLAYGROUND_PATH): - os.makedirs(PLAYGROUND_PATH) - - os.chdir(PLAYGROUND_PATH) + def __init__(self, method_name: str): + CommandTestCase.__init__(self, method_name) def _test_project(self, project_type: str, name: str, *args, test_venv=False, without_ws=False): CLICommands.new(project_type, name, *args) @@ -73,7 +70,6 @@ class NewTestCase(unittest.TestCase): project_path = os.path.abspath(os.path.join(PLAYGROUND_PATH, workspace_name, base, String.convert_to_snake_case(name))) self.assertTrue(os.path.exists(project_path)) self.assertTrue(os.path.join(project_path, f'{name}.json')) - os.chdir(os.path.abspath(os.path.join(os.getcwd(), '../'))) def _test_sub_directory_project(self, project_type: str, directory: str, name: str, workspace_name: str, *args): os.chdir(os.path.abspath(os.path.join(os.getcwd(), workspace_name))) @@ -99,8 +95,6 @@ class NewTestCase(unittest.TestCase): self.assertEqual(build_settings['Main'], f'{String.convert_to_snake_case(name)}.main') self.assertEqual(build_settings['EntryPoint'], name) - os.chdir(os.path.abspath(os.path.join(os.getcwd(), '../'))) - def test_console(self): self._test_project('console', 'test-console', '--ab', '--s', '--venv', test_venv=True) diff --git a/unittests/unittests_cli/publish_test_case.py b/unittests/unittests_cli/publish_test_case.py index 141e374e..9992626b 100644 --- a/unittests/unittests_cli/publish_test_case.py +++ b/unittests/unittests_cli/publish_test_case.py @@ -5,15 +5,16 @@ import shutil import unittest from cpl_core.utils import String +from unittests_cli.abc.command_test_case import CommandTestCase from unittests_cli.constants import PLAYGROUND_PATH from unittests_shared.cli_commands import CLICommands -class PublishTestCase(unittest.TestCase): +class PublishTestCase(CommandTestCase): - def __init__(self, methodName: str): - unittest.TestCase.__init__(self, methodName) + def __init__(self, method_name: str): + CommandTestCase.__init__(self, method_name) self._source = 'publish-test-source' self._project_file = f'src/{String.convert_to_snake_case(self._source)}/{self._source}.json' @@ -39,13 +40,6 @@ class PublishTestCase(unittest.TestCase): CLICommands.new('console', self._source, '--ab', '--s') os.chdir(os.path.join(os.getcwd(), self._source)) - def cleanUp(self): - # remove projects - if not os.path.exists(os.path.abspath(os.path.join(PLAYGROUND_PATH, self._source))): - return - - shutil.rmtree(os.path.abspath(os.path.join(PLAYGROUND_PATH, self._source))) - def _are_dir_trees_equal(self, dir1, dir2): """ found at https://stackoverflow.com/questions/4187564/recursively-compare-two-directories-to-ensure-they-have-the-same-files-and-subdi diff --git a/unittests/unittests_cli/remove_test_case.py b/unittests/unittests_cli/remove_test_case.py index f618e4b8..ac2732f3 100644 --- a/unittests/unittests_cli/remove_test_case.py +++ b/unittests/unittests_cli/remove_test_case.py @@ -3,14 +3,15 @@ import os import unittest from cpl_core.utils import String +from unittests_cli.abc.command_test_case import CommandTestCase from unittests_cli.constants import PLAYGROUND_PATH from unittests_shared.cli_commands import CLICommands -class RemoveTestCase(unittest.TestCase): +class RemoveTestCase(CommandTestCase): - def __init__(self, methodName: str): - unittest.TestCase.__init__(self, methodName) + def __init__(self, method_name: str): + CommandTestCase.__init__(self, method_name) self._source = 'add-test-project' self._target = 'add-test-library' self._project_file = f'src/{String.convert_to_snake_case(self._source)}/{self._source}.json' diff --git a/unittests/unittests_cli/run_test_case.py b/unittests/unittests_cli/run_test_case.py index 7ec9b9dd..23841a79 100644 --- a/unittests/unittests_cli/run_test_case.py +++ b/unittests/unittests_cli/run_test_case.py @@ -1,22 +1,18 @@ import json import os import shutil -import subprocess -import sys import unittest -import pkg_resources - from cpl_core.utils import String - +from unittests_cli.abc.command_test_case import CommandTestCase from unittests_cli.constants import PLAYGROUND_PATH from unittests_shared.cli_commands import CLICommands -class RunTestCase(unittest.TestCase): +class RunTestCase(CommandTestCase): - def __init__(self, methodName: str): - unittest.TestCase.__init__(self, methodName) + def __init__(self, method_name: str): + CommandTestCase.__init__(self, method_name) self._source = 'run-test' self._project_file = f'src/{String.convert_to_snake_case(self._source)}/{self._source}.json' self._appsettings = f'src/{String.convert_to_snake_case(self._source)}/appsettings.json' @@ -63,13 +59,6 @@ class RunTestCase(unittest.TestCase): file.write(f'\t\t{self._test_code}') file.close() - def cleanUp(self): - # remove projects - if not os.path.exists(os.path.abspath(os.path.join(PLAYGROUND_PATH, self._source))): - return - - shutil.rmtree(os.path.abspath(os.path.join(PLAYGROUND_PATH, self._source))) - def test_run(self): CLICommands.run() settings = self._get_appsettings() diff --git a/unittests/unittests_cli/start_test_case.py b/unittests/unittests_cli/start_test_case.py index 3ff09294..b0949f14 100644 --- a/unittests/unittests_cli/start_test_case.py +++ b/unittests/unittests_cli/start_test_case.py @@ -5,15 +5,16 @@ import time import unittest from cpl_core.utils import String +from unittests_cli.abc.command_test_case import CommandTestCase from unittests_cli.constants import PLAYGROUND_PATH from unittests_cli.threads.start_test_thread import StartTestThread from unittests_shared.cli_commands import CLICommands -class StartTestCase(unittest.TestCase): +class StartTestCase(CommandTestCase): - def __init__(self, methodName: str): - unittest.TestCase.__init__(self, methodName) + def __init__(self, method_name: str): + CommandTestCase.__init__(self, method_name) self._source = 'start-test' self._project_file = f'src/{String.convert_to_snake_case(self._source)}/{self._source}.json' self._appsettings = f'src/{String.convert_to_snake_case(self._source)}/appsettings.json' @@ -63,13 +64,6 @@ class StartTestCase(unittest.TestCase): file.write(f'\t\t{self._test_code}') file.close() - def cleanUp(self): - # remove projects - if not os.path.exists(os.path.abspath(os.path.join(PLAYGROUND_PATH, self._source))): - return - - shutil.rmtree(os.path.abspath(os.path.join(PLAYGROUND_PATH, self._source))) - def test_start(self): thread = StartTestThread() thread.start() diff --git a/unittests/unittests_cli/uninstall_test_case.py b/unittests/unittests_cli/uninstall_test_case.py index a552bfc2..b61dfafa 100644 --- a/unittests/unittests_cli/uninstall_test_case.py +++ b/unittests/unittests_cli/uninstall_test_case.py @@ -6,14 +6,15 @@ import sys import unittest from cpl_core.utils import String +from unittests_cli.abc.command_test_case import CommandTestCase from unittests_cli.constants import PLAYGROUND_PATH from unittests_shared.cli_commands import CLICommands -class UninstallTestCase(unittest.TestCase): +class UninstallTestCase(CommandTestCase): - def __init__(self, methodName: str): - unittest.TestCase.__init__(self, methodName) + def __init__(self, method_name: str): + CommandTestCase.__init__(self, method_name) self._source = 'uninstall-test-source' self._project_file = f'src/{String.convert_to_snake_case(self._source)}/{self._source}.json' self._version = '1.7.3' @@ -31,19 +32,12 @@ class UninstallTestCase(unittest.TestCase): def setUp(self): if not os.path.exists(PLAYGROUND_PATH): os.makedirs(PLAYGROUND_PATH) - + os.chdir(PLAYGROUND_PATH) # create projects CLICommands.new('console', self._source, '--ab', '--s') os.chdir(os.path.join(os.getcwd(), self._source)) - def cleanUp(self): - # remove projects - if not os.path.exists(os.path.abspath(os.path.join(PLAYGROUND_PATH, self._source))): - return - - shutil.rmtree(os.path.abspath(os.path.join(PLAYGROUND_PATH, self._source))) - def _get_installed_packages(self) -> dict: reqs = subprocess.check_output([sys.executable, '-m', 'pip', 'freeze']) return dict([tuple(r.decode().split('==')) for r in reqs.split()]) diff --git a/unittests/unittests_cli/update_test_case.py b/unittests/unittests_cli/update_test_case.py index 3b50b38c..b6e40243 100644 --- a/unittests/unittests_cli/update_test_case.py +++ b/unittests/unittests_cli/update_test_case.py @@ -6,14 +6,15 @@ import sys import unittest from cpl_core.utils import String +from unittests_cli.abc.command_test_case import CommandTestCase from unittests_cli.constants import PLAYGROUND_PATH from unittests_shared.cli_commands import CLICommands -class UpdateTestCase(unittest.TestCase): +class UpdateTestCase(CommandTestCase): - def __init__(self, methodName: str): - unittest.TestCase.__init__(self, methodName) + def __init__(self, method_name: str): + CommandTestCase.__init__(self, method_name) self._source = 'install-test-source' self._project_file = f'src/{String.convert_to_snake_case(self._source)}/{self._source}.json' @@ -26,6 +27,15 @@ class UpdateTestCase(unittest.TestCase): self._new_package_name = 'discord.py' self._new_package = f'{self._new_package_name}=={self._new_version}' + def setUp(self): + CLICommands.uninstall(self._old_package) + CLICommands.uninstall(self._new_package) + os.chdir(PLAYGROUND_PATH) + # create projects + CLICommands.new('console', self._source, '--ab', '--s') + os.chdir(os.path.join(os.getcwd(), self._source)) + CLICommands.install(self._old_package) + def _get_project_settings(self): with open(os.path.join(os.getcwd(), self._project_file), 'r', encoding='utf-8') as cfg: # load json @@ -39,22 +49,6 @@ class UpdateTestCase(unittest.TestCase): project_file.write(json.dumps(settings, indent=2)) project_file.close() - def setUp(self): - CLICommands.uninstall(self._old_package) - CLICommands.uninstall(self._new_package) - os.chdir(os.path.abspath(PLAYGROUND_PATH)) - # create projects - CLICommands.new('console', self._source, '--ab', '--s') - os.chdir(os.path.join(os.getcwd(), self._source)) - CLICommands.install(self._old_package) - - def cleanUp(self): - # remove projects - if not os.path.exists(os.path.abspath(os.path.join(PLAYGROUND_PATH, self._source))): - return - - shutil.rmtree(os.path.abspath(os.path.join(PLAYGROUND_PATH, self._source))) - def _get_installed_packages(self) -> dict: reqs = subprocess.check_output([sys.executable, '-m', 'pip', 'freeze']) return dict([tuple(r.decode().split('==')) for r in reqs.split()]) @@ -85,5 +79,3 @@ class UpdateTestCase(unittest.TestCase): packages = self._get_installed_packages() self.assertIn(self._new_package_name, packages) self.assertEqual(self._new_version, packages[self._new_package_name]) - - diff --git a/unittests/unittests_cli/version_test_case.py b/unittests/unittests_cli/version_test_case.py index 18e63b14..d409db77 100644 --- a/unittests/unittests_cli/version_test_case.py +++ b/unittests/unittests_cli/version_test_case.py @@ -12,13 +12,14 @@ import cpl_cli from cpl_core.console import ForegroundColorEnum from termcolor import colored +from unittests_cli.abc.command_test_case import CommandTestCase from unittests_shared.cli_commands import CLICommands -class VersionTestCase(unittest.TestCase): +class VersionTestCase(CommandTestCase): - def __init__(self, methodName: str): - unittest.TestCase.__init__(self, methodName) + def __init__(self, method_name: str): + CommandTestCase.__init__(self, method_name) self._block_banner = "" self._block_version = "" self._block_package_header = "" diff --git a/unittests/unittests_shared/cli_commands.py b/unittests/unittests_shared/cli_commands.py index 8ca6ccfc..ee7821f4 100644 --- a/unittests/unittests_shared/cli_commands.py +++ b/unittests/unittests_shared/cli_commands.py @@ -1,5 +1,6 @@ import os import subprocess +import sys from unittests_cli.constants import CLI_PATH @@ -29,7 +30,8 @@ class CLICommands: for arg in args: command.append(arg) - return subprocess.run(command, env=env_vars, check=True, capture_output=True, text=True).stdout + with subprocess.Popen(command, stdout=subprocess.PIPE, universal_newlines=True) as process: + return process.stdout.read() @classmethod def add(cls, source: str, target: str, output=False): diff --git a/unittests/unittests_translation/translation_test_suite.py b/unittests/unittests_translation/translation_test_suite.py index f1e15132..f0d5a3da 100644 --- a/unittests/unittests_translation/translation_test_suite.py +++ b/unittests/unittests_translation/translation_test_suite.py @@ -1,11 +1,7 @@ -import os -import shutil -import traceback import unittest from typing import Optional from unittest import TestResult -from unittests_cli.constants import PLAYGROUND_PATH from unittests_translation.translation_test_case import TranslationTestCase @@ -25,27 +21,5 @@ class TranslationTestSuite(unittest.TestSuite): for test in active_tests: self.addTests(loader.loadTestsFromTestCase(test)) - def _setup(self): - try: - if os.path.exists(PLAYGROUND_PATH): - shutil.rmtree(os.path.abspath(os.path.join(PLAYGROUND_PATH))) - - os.makedirs(PLAYGROUND_PATH) - os.chdir(PLAYGROUND_PATH) - except Exception as e: - print(f'Setup of {__name__} failed: {traceback.format_exc()}') - - def _cleanup(self): - try: - if self._result is not None and (len(self._result.errors) > 0 or len(self._result.failures) > 0): - return - - if os.path.exists(PLAYGROUND_PATH): - shutil.rmtree(os.path.abspath(os.path.join(PLAYGROUND_PATH))) - except Exception as e: - print(f'Cleanup of {__name__} failed: {traceback.format_exc()}') - def run(self, *args): - self._setup() self._result = super().run(*args) - self._cleanup() -- 2.45.2 From 2fe3912a07409abdc641cc9e25399a840f41938d Mon Sep 17 00:00:00 2001 From: Sven Heidemann Date: Thu, 1 Dec 2022 23:34:00 +0100 Subject: [PATCH 5/6] Fixed version test #129 --- unittests/unittests_shared/cli_commands.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/unittests/unittests_shared/cli_commands.py b/unittests/unittests_shared/cli_commands.py index ee7821f4..eb0f2f1f 100644 --- a/unittests/unittests_shared/cli_commands.py +++ b/unittests/unittests_shared/cli_commands.py @@ -24,14 +24,13 @@ class CLICommands: @staticmethod def _run_with_output(cmd: str, *args) -> str: env_vars = os.environ - # env_vars['CPL_IS_UNITTEST'] = 'NO' + env_vars['CPL_IS_UNITTEST'] = 'NO' command = ['python', CLI_PATH, cmd] for arg in args: command.append(arg) - with subprocess.Popen(command, stdout=subprocess.PIPE, universal_newlines=True) as process: - return process.stdout.read() + return subprocess.run(command, env=env_vars, check=True, capture_output=True, text=True).stdout @classmethod def add(cls, source: str, target: str, output=False): -- 2.45.2 From 05bd5e859366c303a7693182292462321b7216ca Mon Sep 17 00:00:00 2001 From: Sven Heidemann Date: Thu, 1 Dec 2022 23:41:06 +0100 Subject: [PATCH 6/6] Improved query performance test #129 --- unittests/unittests_query/performance_test_case.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/unittests/unittests_query/performance_test_case.py b/unittests/unittests_query/performance_test_case.py index 904cc4e9..d3f9c205 100644 --- a/unittests/unittests_query/performance_test_case.py +++ b/unittests/unittests_query/performance_test_case.py @@ -19,9 +19,9 @@ class PerformanceTestCase(unittest.TestCase): i += 1 def test_range(self): - default = timeit.timeit(lambda: list(self.values), number=COUNT) - enumerable = timeit.timeit(lambda: Enumerable(int, self.values), number=COUNT) - iterable = timeit.timeit(lambda: Iterable(int, self.values), number=COUNT) + default = timeit.timeit(lambda: list(range(0, VALUES)), number=COUNT) + iterable = timeit.timeit(lambda: Iterable.range(0, VALUES), number=COUNT) + enumerable = timeit.timeit(lambda: Enumerable.range(0, VALUES), number=COUNT) print('Range') print(f'd: {default}s') @@ -32,9 +32,9 @@ class PerformanceTestCase(unittest.TestCase): self.assertLess(default, iterable) def test_where_single(self): - default = timeit.timeit(lambda: [x for x in list(self.values) if x == 50], number=COUNT) - iterable = timeit.timeit(lambda: Iterable(int, self.values).where(lambda x: x == 50).single(), number=COUNT) - enumerable = timeit.timeit(lambda: Enumerable(int, self.values).where(lambda x: x == 50).single(), number=COUNT) + default = timeit.timeit(lambda: [x for x in list(range(0, VALUES)) if x == 50], number=COUNT) + iterable = timeit.timeit(lambda: Iterable.range(0, VALUES).where(lambda x: x == 50).single(), number=COUNT) + enumerable = timeit.timeit(lambda: Enumerable.range(0, VALUES).where(lambda x: x == 50).single(), number=COUNT) print('Where single') print(f'd: {default}s') @@ -55,7 +55,7 @@ class PerformanceTestCase(unittest.TestCase): for i in range(VALUES): values.append(TestModel(i, TestModel(i + 1))) - default = timeit.timeit(lambda: [x for x in list(values) if x.tm.value == 50], number=COUNT) + default = timeit.timeit(lambda: [x for x in values if x.tm.value == 50], number=COUNT) iterable = timeit.timeit(lambda: Iterable(TestModel, values).where(lambda x: x.tm.value == 50).single(), number=COUNT) enumerable = timeit.timeit(lambda: Enumerable(TestModel, values).where(lambda x: x.tm.value == 50).single(), number=COUNT) -- 2.45.2