sixty-north/asq

Inherit from Queryable

Closed this issue · 4 comments

Hello,

I'd like to create a custom collection which inherits from Queryable but which also have some specific methods.

My first idea was to do:

from asq.queryables import Queryable


class DoableResults(Queryable):
    def __init__(self, context, results) -> None:
        super().__init__(results)
        self._context = context

    def do_it(self):
        print("Let's do it with %s" % self._context)
    

unfortunately if I do

results = DoableResults(None, [1, 2, 3, 4, 5, 6])
results.do_it()

works fine but if I do

results.where(lambda n: n>2)

it returns a Queryable... I was expecting it to return a DoableResults

I had a look at doc to see if you wrote some information about that
https://asq.readthedocs.io/en/latest/search.html?q=inheritance
but couldn't find much information

So what is a proper way to inherit from Queryable?

I noticed https://asq.readthedocs.io/en/latest/narrative.html#extending-asq

but I don't think it's a correct way to do what I'm looking for as it will create a do_it method for any Queryable which is not what I'm looking for.

Some help to tackle this will be great!

Kind regards

This is a good question. It's a while since I worked with asq (if it ain't broke, don't fix it), so I had to refresh my memory. It looks like all the places that Queryable returns another Queryable are done by calling the Queryable._create(iterable) or `Queryable._create_ordered(iterable, direction, func) factory methods. The docstrings of both say "This method exists to allow it to be overridden by subclasses of Queryable.", so I was obviously planning for this day.

Try overriding those, and please let me know how you get on.

Ok, thanks, I saw

asq/asq/queryables.py

Lines 92 to 120 in 389f647

def _create(self, iterable):
'''Create a Queryable using the the supplied iterable.
This method exists to allow it to be overridden by subclasses of
Queryable.
Args:
iterable: An iterable.
Returns:
A Queryable constructed using the supplied iterable.
Raises:
TypeError: If the argument is not in fact iterable.
'''
return Queryable(iterable)
def _create_ordered(self, iterable, direction, func):
'''Create an ordered iterable using the supplied iterable.
This method exists to allow it to be overridden by subclasses of
Queryable.
Args:
iterable: The iterable sequence to be ordered.
direction: +1 for ascending, -1 for descending.
func: The function to select the sorting key.
'''
return OrderedQueryable(iterable, direction, func)

I wonder (for at least Queryable._create(iterable) if it wouldn't help to turn it to a classmethod.

But I'm not sure how to tackle Queryable._create_ordered(iterable, direction, func) in such a case.

I will try that tomorrow because I'm quite busy today.

It certainly could be a class method in its current form, but it's not clear to me that that would also be true for all possible subclass implementations.

It could also have a default implementation of something like:

def _create(self, iterable): 
         return type(self)(iterable)

so then in simple cases, like yours, it wouldn't need to be overridden.

This is how I handled this

from asq.queryables import Queryable, OrderedQueryable


class Doable:
    def do_it(self):
        print("Let's do it with contex=%s" % self._context)


class DoableResults(Queryable, Doable):
    def __init__(self, context, results) -> None:
        super().__init__(results)
        self._context = context

    def _create(self, iterable):
        return type(self)(self._context, iterable)

    def _create_ordered(self, iterable, direction, func):
        return OrderedDoableResults(self._context, iterable, direction, func)


class OrderedDoableResults(OrderedQueryable, Doable):
    def __init__(self, context, iterable, direction, func) -> None:
        super().__init__(iterable, direction, func)
        self._context = context


results = DoableResults(
    None, [{"v": 1}, {"v": 2}, {"v": 3}, {"v": 4}, {"v": 5}, {"v": 6}]
)
print(results.count())
results.do_it()
print()
print(results.where(lambda n: n["v"] > 2).count())
results.where(lambda n: n["v"] > 2).do_it()
print()
print(results.order_by(lambda n: n["v"]).count())
results.order_by(lambda n: n["v"]).do_it()