Search code examples
pythonpeeweeflask-peewee

Building a FIQL query with peewee


I'm attempting to create a function that will take a FIQL formatted string and return a peewee expression.

Let's say I have url params in FIQL format like so:

fiql_str = "name==Foo,(country==USA;city==Kansas)"

Using a FIQL Parser I am able to get this object back:

['OR', ('name', '==', 'Foo'), ['AND', ('country', '==', 'USA'), ('city', '==', 'Kansas')]]

What I'd like to do, is to create a function that takes the above object, and creates an expression that peewee can understand. I'm used to django & Q objects, where I can chain together expressions like so:

fm = Q()
for mapping in mappings:
    fm |= Q(subscription__approver=mapping.user)
return self.filter(fm)

I've tried mimicking this with peewee's Query Builder / Node like so:

def map_to_filter(expressions, node):
    expression = expressions.pop(0)
    if type(expression) == str:
        if expression == "OR":
            node |= map_to_filter(expressions, node)
        if expression == "AND":
            node &= map_to_filter(expressions, node)
    elif type(expression) == tuple:
        return getattr(Store, expression[0]) + expression[1] + expression[2]
    elif type(expression) == list:
        map_to_filter(expression, node)
    return node

result = map_to_filter(expressions, peewee.Node())

But I am getting a NotImplementedError:

/lib/python3.7/site-packages/peewee.py in __sql__(self, ctx)
    616
    617     def __sql__(self, ctx):
--> 618         raise NotImplementedError
    619
    620     @staticmethod

NotImplementedError:

Is it possible to build such a function? Otherwise, what other tools/pluggins are available for troubleshooting this?


Solution

  • Your issue comes from using a bare Node, which does not correspond to any SQL (hence, no sql method).

    I'd suggest accumulating a list and using functools.reduce() to combine them.

    e.g.,

    list_of_conds = [
        (model.field1 == 'val1'),
        (model.field2 == 'bar')]
    reduce(operator.and_, list_of_conds)
    

    You can switch your reduce function to be operator.or_ where necessary and proceed using a depth-first search, as you have.