diff --git a/src/cpl_query/base/__init__.py b/src/cpl_query/base/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/cpl_query/extension/iterable_abc.py b/src/cpl_query/base/queryable_abc.py similarity index 69% rename from src/cpl_query/extension/iterable_abc.py rename to src/cpl_query/base/queryable_abc.py index e45d7815..13fd2c4c 100644 --- a/src/cpl_query/extension/iterable_abc.py +++ b/src/cpl_query/base/queryable_abc.py @@ -1,26 +1,8 @@ -from abc import ABC, abstractmethod -from typing import Optional, Callable, Union, Iterable +from abc import abstractmethod, ABC +from typing import Optional, Callable, Union -class IterableABC(ABC, list): - r"""ABC to define functions on list - """ - - @abstractmethod - def __init__(self, t: type = None, values: list = None): - list.__init__(self) - - if t == any: - t = None - self._type = t - - if values is not None: - for value in values: - self.append(value) - - @property - def type(self) -> type: - return self._type +class QueryableABC(ABC): @abstractmethod def all(self, func: Callable) -> bool: @@ -52,22 +34,6 @@ class IterableABC(ABC, list): """ pass - def append(self, __object: object) -> None: - r"""Adds element to list - - Parameter - --------- - __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) - - super().append(__object) - @abstractmethod def average(self, func: Callable = None) -> Union[int, float, complex]: r"""Returns average value of list @@ -114,7 +80,7 @@ class IterableABC(ABC, list): pass @abstractmethod - def distinct(self, func: Callable = None) -> 'IterableABC': + def distinct(self, func: Callable = None) -> 'QueryableABC': r"""Returns list without redundancies Parameter @@ -124,7 +90,7 @@ class IterableABC(ABC, list): Returns ------- - :class: `cpl_query.extension.iterable_abc.IterableABC` + :class: `cpl_query.base.queryable_abc.QueryableABC` """ pass @@ -158,19 +124,6 @@ class IterableABC(ABC, list): """ pass - def extend(self, __iterable: Iterable) -> 'IterableABC': - r"""Adds elements of given list to list - - Parameter - --------- - __iterable: :class: `cpl_query.extension.iterable.Iterable` - index - """ - for value in __iterable: - self.append(value) - - return self - @abstractmethod def last(self) -> any: r"""Returns last element @@ -253,7 +206,7 @@ class IterableABC(ABC, list): pass @abstractmethod - def order_by(self, func: Callable) -> 'IterableABC': + def order_by(self, func: Callable) -> 'QueryableABC': r"""Sorts elements by function in ascending order Parameter @@ -263,12 +216,12 @@ class IterableABC(ABC, list): Returns ------- - :class: `cpl_query.extension.iterable_abc.IterableABC` + :class: `cpl_query.base.queryable_abc.QueryableABC` """ pass @abstractmethod - def order_by_descending(self, func: Callable) -> 'IterableABC': + def order_by_descending(self, func: Callable) -> 'QueryableABC': r"""Sorts elements by function in descending order Parameter @@ -278,35 +231,37 @@ class IterableABC(ABC, list): Returns ------- - :class: `cpl_query.extension.iterable_abc.IterableABC` + :class: `cpl_query.base.queryable_abc.QueryableABC` """ pass @abstractmethod - def reverse(self) -> 'IterableABC': + def reverse(self) -> 'QueryableABC': r"""Reverses list Returns ------- - :class: `cpl_query.extension.iterable_abc.IterableABC` + :class: `cpl_query.base.queryable_abc.QueryableABC` """ pass - def select(self, _f: Callable) -> 'IterableABC': + @abstractmethod + def select(self, _f: Callable) -> 'QueryableABC': r"""Formats each element of list to a given format Returns ------- - :class: `cpl_query.extension.iterable_abc.IterableABC` + :class: `cpl_query.base.queryable_abc.QueryableABC` """ pass - def select_many(self, _f: Callable) -> 'IterableABC': + @abstractmethod + def select_many(self, _f: Callable) -> 'QueryableABC': r"""Flattens resulting lists to one Returns ------- - :class: `cpl_query.extension.iterable_abc.IterableABC` + :class: `cpl_query.base.queryable_abc.QueryableABC` """ pass @@ -336,7 +291,7 @@ class IterableABC(ABC, list): pass @abstractmethod - def skip(self, index: int) -> 'IterableABC': + def skip(self, index: int) -> 'QueryableABC': r"""Skips all elements from index Parameter @@ -346,12 +301,12 @@ class IterableABC(ABC, list): Returns ------- - :class: `cpl_query.extension.iterable_abc.IterableABC` + :class: `cpl_query.base.queryable_abc.QueryableABC` """ pass @abstractmethod - def skip_last(self, index: int) -> 'IterableABC': + def skip_last(self, index: int) -> 'QueryableABC': r"""Skips all elements after index Parameter @@ -361,7 +316,7 @@ class IterableABC(ABC, list): Returns ------- - :class: `cpl_query.extension.iterable_abc.IterableABC` + :class: `cpl_query.base.queryable_abc.QueryableABC` """ pass @@ -381,7 +336,7 @@ class IterableABC(ABC, list): pass @abstractmethod - def take(self, index: int) -> 'IterableABC': + def take(self, index: int) -> 'QueryableABC': r"""Takes all elements from index Parameter @@ -391,12 +346,12 @@ class IterableABC(ABC, list): Returns ------- - :class: `cpl_query.extension.iterable_abc.IterableABC` + :class: `cpl_query.base.queryable_abc.QueryableABC` """ pass @abstractmethod - def take_last(self, index: int) -> 'IterableABC': + def take_last(self, index: int) -> 'QueryableABC': r"""Takes all elements after index Parameter @@ -406,21 +361,12 @@ class IterableABC(ABC, list): Returns ------- - :class: `cpl_query.extension.iterable_abc.IterableABC` + :class: `cpl_query.base.queryable_abc.QueryableABC` """ pass - def to_list(self) -> list: - r"""Converts :class: `cpl_query.extension.iterable_abc.IterableABC` to :class: `list` - - Returns - ------- - :class: `list` - """ - return list(self) - @abstractmethod - def where(self, func: Callable) -> 'IterableABC': + def where(self, func: Callable) -> 'QueryableABC': r"""Select element by function Parameter @@ -430,6 +376,6 @@ class IterableABC(ABC, list): Returns ------- - :class: `cpl_query.extension.iterable_abc.IterableABC` + :class: `cpl_query.base.queryable_abc.QueryableABC` """ pass diff --git a/src/cpl_query/enumerable/__init__.py b/src/cpl_query/enumerable/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/cpl_query/enumerable/enumerable_abc.py b/src/cpl_query/enumerable/enumerable_abc.py new file mode 100644 index 00000000..c2de4139 --- /dev/null +++ b/src/cpl_query/enumerable/enumerable_abc.py @@ -0,0 +1,127 @@ +import itertools +from abc import abstractmethod +from typing import Union + +from cpl_query.enumerable.enumerable_values import EnumerableValues + + +class EnumerableABC: + + @abstractmethod + def __init__(self, t: type = None, values: Union[list, iter] = None): + if t == any: + t = None + self._type = t + + self._values = EnumerableValues(values) + 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 __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) -> object: + r"""Gets item in enumerable at specified zero-based index + + Parameter + -------- + n: the index of the item to get + + Returns + ------- + The element at the specified index. + + Raises + ------ + IndexError if n > number of elements in the iterable + """ + for i, e in enumerate(self): + if i == n: + return e + + def __repr__(self): + return f'' + + @property + def type(self) -> type: + return self._type + + 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 = EnumerableValues([*self._values, __object]) + + def count(self) -> int: + r"""Returns count of elements + + Returns + ------- + int + """ + return sum(1 for element in self) + + def clear(self): + r"""Removes all elements + """ + del self._values + self._values = [] + + @staticmethod + def empty(): + r"""Returns an empty enumerable + + Returns + ------- + Enumerable object that contains no elements + """ + return EnumerableABC() + + @staticmethod + def range(start: int, length: int) -> 'EnumerableABC': + return EnumerableABC(int, range(start, start + length, 1)) + + 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 = EnumerableValues([x for x in self.to_list() if x != __object]) + + def to_list(self) -> list: + r"""Converts :class: `cpl_query.enumerable.enumerable_abc.EnumerableABC` to :class: `list` + + Returns + ------- + :class: `list` + """ + return [x for x in self] diff --git a/src/cpl_query/enumerable/enumerable_values.py b/src/cpl_query/enumerable/enumerable_values.py new file mode 100644 index 00000000..455daa61 --- /dev/null +++ b/src/cpl_query/enumerable/enumerable_values.py @@ -0,0 +1,31 @@ +import io +import itertools + + +class EnumerableValues: + def __init__(self, data): + if data is None: + data = [] + + if not hasattr(data, '__iter__'): + raise TypeError('RepeatableIterable must be instantiated with an iterable object') + + is_generator = hasattr(data, 'gi_running') or isinstance(data, io.TextIOBase) + self._data = data if not is_generator else [i for i in data] + self._len = sum(1 for item in self._data) + self.cycle = itertools.cycle(self._data) + + def __len__(self): + return self._len + + def __iter__(self): + i = 0 + while i < len(self): + yield next(self.cycle) + i += 1 + + def __next__(self): + return self.next() + + def next(self): + return next(self.cycle) diff --git a/src/cpl_query/extension/__init__.py b/src/cpl_query/extension/__init__.py index dee0d4f3..86f8db09 100644 --- a/src/cpl_query/extension/__init__.py +++ b/src/cpl_query/extension/__init__.py @@ -21,11 +21,7 @@ from collections import namedtuple # imports: -from .iterable_abc import IterableABC -from .iterable import Iterable from .list import List -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='2') diff --git a/src/cpl_query/extension/lazy_list.py b/src/cpl_query/extension/lazy_list.py new file mode 100644 index 00000000..3c0a65ff --- /dev/null +++ b/src/cpl_query/extension/lazy_list.py @@ -0,0 +1,9 @@ +from cpl_query.enumerable.enumerable_abc import EnumerableABC + + +class LazyList(EnumerableABC): + r"""Implementation of :class: `cpl_query.enumerable.enumerable_abc.EnumerableABC` + """ + + def __init__(self, t: type = None, values: list = None): + EnumerableABC.__init__(self, t, values) diff --git a/src/cpl_query/extension/list.py b/src/cpl_query/extension/list.py index a99ca58f..d658f1d2 100644 --- a/src/cpl_query/extension/list.py +++ b/src/cpl_query/extension/list.py @@ -1,4 +1,4 @@ -from cpl_query.extension.iterable import Iterable +from cpl_query.iterable.iterable import Iterable class List(Iterable): diff --git a/src/cpl_query/iterable/__init__.py b/src/cpl_query/iterable/__init__.py new file mode 100644 index 00000000..7c2d7a05 --- /dev/null +++ b/src/cpl_query/iterable/__init__.py @@ -0,0 +1,30 @@ +# -*- coding: utf-8 -*- + +""" +cpl-query sh-edraft Common Python library Query +~~~~~~~~~~~~~~~~~~~ + +sh-edraft Common Python library Python integrated Queries + +:copyright: (c) 2021 - 2022 sh-edraft.de +:license: MIT, see LICENSE for more details. + +""" + +__title__ = 'cpl_query.iterable' +__author__ = 'Sven Heidemann' +__license__ = 'MIT' +__copyright__ = 'Copyright (c) 2021 - 2022 sh-edraft.de' +__version__ = '2022.10.2' + +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='2') diff --git a/src/cpl_query/extension/iterable.py b/src/cpl_query/iterable/iterable.py similarity index 92% rename from src/cpl_query/extension/iterable.py rename to src/cpl_query/iterable/iterable.py index e23a6d06..bd6c761d 100644 --- a/src/cpl_query/extension/iterable.py +++ b/src/cpl_query/iterable/iterable.py @@ -1,7 +1,7 @@ from typing import Callable, Optional, Union -from cpl_query.extension.iterable_abc import IterableABC -from cpl_query.extension.ordered_iterable_abc import OrderedIterableABC +from cpl_query.iterable.iterable_abc import IterableABC +from cpl_query.iterable.ordered_iterable_abc import OrderedIterableABC from cpl_query.query import Query @@ -57,13 +57,13 @@ class Iterable(IterableABC): def order_by(self, func: Callable) -> OrderedIterableABC: res = Query.order_by(self, func) - from cpl_query.extension.ordered_iterable import OrderedIterable + from cpl_query.iterable.ordered_iterable import OrderedIterable res.__class__ = OrderedIterable return res def order_by_descending(self, func: Callable) -> OrderedIterableABC: res = Query.order_by_descending(self, func) - from cpl_query.extension.ordered_iterable import OrderedIterable + from cpl_query.iterable.ordered_iterable import OrderedIterable res.__class__ = OrderedIterable return res diff --git a/src/cpl_query/iterable/iterable_abc.py b/src/cpl_query/iterable/iterable_abc.py new file mode 100644 index 00000000..7737c7f1 --- /dev/null +++ b/src/cpl_query/iterable/iterable_abc.py @@ -0,0 +1,64 @@ +from abc import ABC, abstractmethod +from typing import Optional, Callable, Union, Iterable + +from cpl_query.base.queryable_abc import QueryableABC +from cpl_query.enumerable.enumerable_abc import EnumerableABC + + +class IterableABC(QueryableABC, list): + r"""ABC to define functions on list + """ + + @abstractmethod + def __init__(self, t: type = None, values: list = None): + list.__init__(self) + + if t == any: + t = None + self._type = t + + if values is not None: + for value in values: + self.append(value) + + @property + def type(self) -> type: + return self._type + + def append(self, __object: object) -> None: + r"""Adds element to list + + Parameter + --------- + __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) + + super().append(__object) + + def extend(self, __iterable: Iterable) -> 'IterableABC': + r"""Adds elements of given list to list + + Parameter + --------- + __iterable: :class: `cpl_query.extension.iterable.Iterable` + index + """ + for value in __iterable: + self.append(value) + + return self + + def to_list(self) -> list: + r"""Converts :class: `cpl_query.enumerable.enumerable_abc.EnumerableABC` to :class: `list` + + Returns + ------- + :class: `list` + """ + return list(self) diff --git a/src/cpl_query/extension/ordered_iterable.py b/src/cpl_query/iterable/ordered_iterable.py similarity index 85% rename from src/cpl_query/extension/ordered_iterable.py rename to src/cpl_query/iterable/ordered_iterable.py index 226a977e..0c0c2263 100644 --- a/src/cpl_query/extension/ordered_iterable.py +++ b/src/cpl_query/iterable/ordered_iterable.py @@ -1,7 +1,7 @@ from collections.abc import Callable -from cpl_query.extension.iterable import Iterable -from cpl_query.extension.ordered_iterable_abc import OrderedIterableABC +from cpl_query.iterable.iterable import Iterable +from cpl_query.iterable.ordered_iterable_abc import OrderedIterableABC from cpl_query.query import Query diff --git a/src/cpl_query/extension/ordered_iterable_abc.py b/src/cpl_query/iterable/ordered_iterable_abc.py similarity index 94% rename from src/cpl_query/extension/ordered_iterable_abc.py rename to src/cpl_query/iterable/ordered_iterable_abc.py index bd10a917..5a11d3a6 100644 --- a/src/cpl_query/extension/ordered_iterable_abc.py +++ b/src/cpl_query/iterable/ordered_iterable_abc.py @@ -1,7 +1,7 @@ from abc import abstractmethod from collections.abc import Callable -from cpl_query.extension.iterable_abc import IterableABC +from cpl_query.iterable.iterable_abc import IterableABC class OrderedIterableABC(IterableABC): diff --git a/src/cpl_query/query.py b/src/cpl_query/query.py index b904c54b..bedd61dc 100644 --- a/src/cpl_query/query.py +++ b/src/cpl_query/query.py @@ -2,8 +2,8 @@ from typing import Callable, Union, Optional from cpl_query._helper import is_number from cpl_query.exceptions import ArgumentNoneException, ExceptionArgument, InvalidTypeException, IndexOutOfRangeException -from cpl_query.extension.iterable_abc import IterableABC -from cpl_query.extension.ordered_iterable_abc import OrderedIterableABC +from cpl_query.iterable.iterable_abc import IterableABC +from cpl_query.iterable.ordered_iterable_abc import OrderedIterableABC class Query: diff --git a/unittests/unittests_query/enumerable_test_case.py b/unittests/unittests_query/enumerable_test_case.py new file mode 100644 index 00000000..ea09219a --- /dev/null +++ b/unittests/unittests_query/enumerable_test_case.py @@ -0,0 +1,68 @@ +import unittest + +from cpl_query.extension.lazy_list import LazyList +from cpl_query.extension.list import List + + +class EnumerableTestCase(unittest.TestCase): + + def setUp(self) -> None: + self._list = LazyList(int) + + def _clear(self): + self._list.clear() + self.assertEqual(self._list, []) + + 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') + + def test_default(self): + self.assertEqual(LazyList.empty().to_list(), []) + self.assertEqual(LazyList.range(0, 100).to_list(), list(range(0, 100))) + + def test_iter(self): + n = 0 + elements = LazyList.range(0, 100) + while n < 100: + self.assertEqual(elements.next(), n) + n += 1 + + def test_for(self): + n = 0 + for i in LazyList.range(0, 100): + self.assertEqual(i, n) + n += 1 + + def test_get(self): + self._list.add(1) + self._list.add(2) + self._list.add(3) + + self.assertEqual(self._list[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/query_test_suite.py b/unittests/unittests_query/query_test_suite.py index 35c3fe03..bdfc060b 100644 --- a/unittests/unittests_query/query_test_suite.py +++ b/unittests/unittests_query/query_test_suite.py @@ -1,5 +1,6 @@ import unittest +from unittests_query.enumerable_test_case import EnumerableTestCase from unittests_query.iterable_test_case import IterableTestCase from unittests_query.query_test_case import QueryTestCase @@ -11,6 +12,7 @@ class QueryTestSuite(unittest.TestSuite): loader = unittest.TestLoader() self.addTests(loader.loadTestsFromTestCase(QueryTestCase)) + self.addTests(loader.loadTestsFromTestCase(EnumerableTestCase)) self.addTests(loader.loadTestsFromTestCase(IterableTestCase)) def run(self, *args):