diff --git a/src/cpl_query/_query/order_by.py b/src/cpl_query/_query/order_by.py new file mode 100644 index 00000000..12cea7b3 --- /dev/null +++ b/src/cpl_query/_query/order_by.py @@ -0,0 +1,28 @@ +from collections import Callable + +from cpl_query.extension.iterable_abc import IterableABC +from cpl_query.extension.ordered_iterable_abc import OrderedIterableABC + + +def order_by_query(_list: IterableABC, _func: Callable) -> OrderedIterableABC: + result = OrderedIterableABC(_func) + _list.sort(key=_func) + result.extend(_list) + return result + + +def order_by_descending_query(_list: IterableABC, _func: Callable) -> OrderedIterableABC: + result = OrderedIterableABC(_func) + _list.sort(key=_func, reverse=True) + result.extend(_list) + return result + + +def then_by_query(_list: OrderedIterableABC, _func: Callable) -> OrderedIterableABC: + _list.sort(key=_func) + return _list + + +def then_by_descending_query(_list: OrderedIterableABC, _func: Callable) -> OrderedIterableABC: + _list.sort(key=_func, reverse=True) + return _list diff --git a/src/cpl_query/extension/iterable.py b/src/cpl_query/extension/iterable.py new file mode 100644 index 00000000..50926fa6 --- /dev/null +++ b/src/cpl_query/extension/iterable.py @@ -0,0 +1,53 @@ +from typing import Optional, Callable + +from .ordered_iterable import OrderedIterable +from .ordered_iterable_abc import OrderedIterableABC +from .._query.any_query import any_query +from .._query.first_query import first_or_default_query, first_query +from .._query.for_each_query import for_each_query +from .._query.order_by import order_by_query, order_by_descending_query +from .._query.single_query import single_query, single_or_default_query +from .._query.where_query import where_query +from cpl_query.extension.iterable_abc import IterableABC + + +class Iterable(IterableABC): + + def __init__(self): + IterableABC.__init__(self) + + def any(self, func: str) -> bool: + return any_query(self, func) + + def first(self) -> any: + return first_query(self) + + def first_or_default(self) -> Optional[any]: + return first_or_default_query(self) + + def for_each(self, func: Callable): + for_each_query(self, func) + + def order_by(self, func: Callable) -> OrderedIterableABC: + res = order_by_query(self, func) + res.__class__ = OrderedIterable + return res + + def order_by_descending(self, func: Callable) -> OrderedIterableABC: + res = order_by_descending_query(self, func) + res.__class__ = OrderedIterable + return res + + def single(self) -> any: + return single_query(self) + + def single_or_default(self) -> Optional[any]: + return single_or_default_query(self) + + def where(self, func: str) -> IterableABC: + res = where_query(self, func) + res.__class__ = Iterable + return res + + def to_list(self) -> list: + return list(self) diff --git a/src/cpl_query/extension/iterable_abc.py b/src/cpl_query/extension/iterable_abc.py index 0aceedf1..e4bf8f80 100644 --- a/src/cpl_query/extension/iterable_abc.py +++ b/src/cpl_query/extension/iterable_abc.py @@ -20,6 +20,12 @@ class IterableABC(ABC, list): @abstractmethod def for_each(self, func: Callable): pass + @abstractmethod + def order_by(self, func: Callable): pass + + @abstractmethod + def order_by_descending(self, func: Callable): pass + @abstractmethod def single(self): pass diff --git a/src/cpl_query/extension/list.py b/src/cpl_query/extension/list.py index b8eebcf3..b216b18f 100644 --- a/src/cpl_query/extension/list.py +++ b/src/cpl_query/extension/list.py @@ -1,40 +1,12 @@ -from typing import Optional, Callable - -from .._query.any_query import any_query -from .._query.first_query import first_or_default_query, first_query -from .._query.for_each_query import for_each_query -from .._query.single_query import single_query, single_or_default_query -from .._query.where_query import where_query -from cpl_query.extension.iterable_abc import IterableABC +from cpl_query.extension.iterable import Iterable -class List(IterableABC): +class List(Iterable): - def __init__(self): - IterableABC.__init__(self) + def __init__(self, t: type = None, values: list = None): + Iterable.__init__(self) - def any(self, func: str) -> bool: - return any_query(self, func) + self._type = t - def first(self) -> any: - return first_query(self) - - def first_or_default(self) -> Optional[any]: - return first_or_default_query(self) - - def for_each(self, func: Callable): - for_each_query(self, func) - - def single(self) -> any: - return single_query(self) - - def single_or_default(self) -> Optional[any]: - return single_or_default_query(self) - - def where(self, func: str) -> IterableABC: - res = where_query(self, func) - res.__class__ = List - return res - - def to_list(self) -> list: - return list(self) + if values is not None: + self.extend(values) diff --git a/src/cpl_query/extension/ordered_iterable.py b/src/cpl_query/extension/ordered_iterable.py new file mode 100644 index 00000000..d7c168e9 --- /dev/null +++ b/src/cpl_query/extension/ordered_iterable.py @@ -0,0 +1,19 @@ +from abc import ABC +from collections import Callable + +from .._query.order_by import then_by_query, then_by_descending_query +from cpl_query.extension.ordered_iterable_abc import OrderedIterableABC + + +class OrderedIterable(OrderedIterableABC, ABC): + + def __init__(self): + OrderedIterableABC.__init__(self) + + def then_by(self, _func: Callable) -> OrderedIterableABC: + self._funcs.append(_func) + return then_by_query(self, lambda *args: [f(*args) for f in self._funcs]) + + def then_by_descending(self, _func: Callable) -> OrderedIterableABC: + self._funcs.append(_func) + return then_by_descending_query(self, lambda *args: [f(*args) for f in self._funcs]) diff --git a/src/cpl_query/extension/ordered_iterable_abc.py b/src/cpl_query/extension/ordered_iterable_abc.py new file mode 100644 index 00000000..f64e15bd --- /dev/null +++ b/src/cpl_query/extension/ordered_iterable_abc.py @@ -0,0 +1,20 @@ +from abc import abstractmethod +from collections import Callable + +from cpl_query.extension.iterable_abc import IterableABC + + +class OrderedIterableABC(IterableABC): + + @abstractmethod + def __init__(self, _func: Callable = None): + IterableABC.__init__(self) + self._funcs: list[Callable] = [] + if _func is not None: + self._funcs.append(_func) + + @abstractmethod + def then_by(self, func: Callable) -> 'OrderedIterableABC': pass + + @abstractmethod + def then_by_descending(self, func: Callable) -> 'OrderedIterableABC': pass diff --git a/src/cpl_query/tests/query_test.py b/src/cpl_query/tests/query_test.py index c85c2dec..4c9ca6dc 100644 --- a/src/cpl_query/tests/query_test.py +++ b/src/cpl_query/tests/query_test.py @@ -10,7 +10,7 @@ from cpl_query.tests.models import User, Address class QueryTest(unittest.TestCase): def setUp(self) -> None: - self._tests = List() + self._tests = List(User) self._t_user = User( 'Test user', Address( @@ -24,9 +24,9 @@ class QueryTest(unittest.TestCase): def _generate_test_data(self): for i in range(0, 100): user = User( - String.random_string(string.ascii_letters, 8), + String.random_string(string.ascii_letters, 8).lower(), Address( - String.random_string(string.ascii_letters, 10), + String.random_string(string.ascii_letters, 10).lower(), randint(0, 10) ) ) @@ -85,6 +85,42 @@ class QueryTest(unittest.TestCase): self.assertEqual(len(users), len(self._tests)) + def test_order_by(self): + res = self._tests.order_by(lambda user: user.address.street) + res2 = self._tests.order_by(lambda user: user.address.nr) + s_res = self._tests + s_res.sort(key=lambda user: user.address.street) + + self.assertEqual(res, s_res) + s_res.sort(key=lambda user: user.address.nr) + self.assertEqual(res2, s_res) + + def test_order_by_descending(self): + res = self._tests.order_by_descending(lambda user: user.address.street) + res2 = self._tests.order_by_descending(lambda user: user.address.nr) + s_res = self._tests + s_res.sort(key=lambda user: user.address.street, reverse=True) + + self.assertEqual(res, s_res) + s_res.sort(key=lambda user: user.address.nr, reverse=True) + self.assertEqual(res2, s_res) + + def test_then_by(self): + res = self._tests.order_by(lambda user: user.address.street[0]).then_by(lambda user: user.address.nr) + + s_res = self._tests + s_res.sort(key=lambda user: (user.address.street[0], user.address.nr)) + + self.assertEqual(res, s_res) + + def test_then_by_descending(self): + res = self._tests.order_by_descending(lambda user: user.address.street[0]).then_by_descending(lambda user: user.address.nr) + + s_res = self._tests + s_res.sort(key=lambda user: (user.address.street[0], user.address.nr), reverse=True) + + self.assertEqual(res, s_res) + def test_single(self): res = self._tests.where(f'User.address.nr == {self._t_user.address.nr}') s_res = self._tests.where(f'User.address.nr == {self._t_user.address.nr}').single()