List querying capabilities in Python 3.x Either out of boredom, or my odd fascination with LINQ and SQL, this, thing, was created. Essentially, it provides list querying capabilities. At the moment, you can only query lists based on conditions, and sort them. Here's the source.

py_listq.py

import operator as oper


class QueryableListCondition:
    """
    A class used for conditions in
    QueryableList.where(*conditions).
    """
    def __init__(self, _operator: str, _value):
        self._operator = _operator.lower()
        self._value = _value
        self._operators = {
            "eq": oper.eq,
            "ne": oper.ne,
            "lt": oper.lt,
            "gt": oper.gt,
            "le": oper.le,
            "ge": oper.ge
        }

    def compare(self, value):
        """
        Compares the values, with value
        as the first compared value. Compari-
        sons look something like this:

            value operator self.value
        """
        return self._operators[self._operator](value, self._value)

    def reverse_compare(self, value):
        """
        Compares the values with self.value
        as the second compared value. Compari-
        sons look something like this:

            value operator self.value
        """
        return self._operators[self._operator](self._value, value)


class QueryableList:
    """
    A special class which is like a
    default Python list, except it 
    has querying capabilities.
    """
    def __init__(self, _items: list):
        self._items = _items

    def select(self):
        """
        'Select' self._items and return it as
        a list. This is used in QueryableList.
        where as the first argument.
        """
        return self._items

    def where(self, query_list: list, compare_type: str, *conditions: QueryableListCondition):
        """
        Select elements from a QueryableList
        based on conditions.
        """
        result_list = []
        for condition in conditions:
            for item in self._items:
                if compare_type.lower() == "non-reverse":
                    if condition.compare(item) and item not in result_list:
                        result_list.append(item)
                if compare_type.lower() == "reverse":
                    if condition.reverse_compare(item) and item not in result_list:
                        result_list.append(item)
        return result_list

    def order(self, query_list: list, reverse_results=False):
        """
        Returns a sorted version of the list.
        if reverse=True then the list is
        ordererd in reverse.
        """
        return sorted(query_list, reverse=reverse_results)


Also, for reference, here's a simple example program:

test.py

from py_listq import QueryableList, QueryableListCondition

data = QueryableList([6, 2, 7, 1223, 56, 14, 6, 2435, 234, 6, 2134, 124235, 243621, 74, 378, 3, 45])
result = data.order(
    data.where(
        data.select(), 
        "non-reverse", 
        QueryableListCondition("gt", 1000)
    ), 
    reverse_results=True
)

print(result)


I feel like I shouldn't have implemented this in an Object-Oriented way, and it feels, clunky, to use. So if you have any tips on those, they'd be very much appreciated.
The _operators attribute is not specific to an instance but a common mapping table that can be shared by all instances, so it could be turned into a class attribute instead.

The case of an unsupported operator is not handled. The class happily keys the user create a condition with an unsupported operator, and the program will crash later. Always validate the received function parameters, and signal problems as early as possible.

After the condition is created, there's no need to hold on to the string representation of the operator only to lookup the real operator in the common mapping table. Better to lookup once, in the constructor, and store the real operator, not its string representation.

If a condition has two modes, non-reverse and reverse, consider using a boolean to represent that. Forcing the user to remember to pass on the right magic string is not very user friendly and prone to errors. If you use a boolean, there are 2 possible values, instead of an infinite spacer of invalid values. More intuitive api, and higher chance that users get it right.

These conditions cannot both be true at the same time:

            if compare_type.lower() == "non-reverse":
                # ...
            if compare_type.lower() == "reverse":


So the second one should be an elif. Converting the compare type variable to lower case twice is also not pretty.

I think par of the reason the current implementation feels clunky is that it is not fluent. For example, a fluent api to select a list of name-email pairs from a collection objects, matching a name, ordered by age, look something like this:

queryset.where(name="Jack").order_by("age").select("name", "email")


disclaimer: inspired by queries in Django