Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

sqlalchemy - elegant way to deal with several optional filters?

Suppose I have a query method with serveral optional filters. What I want to achieve is, If I pass some not None value to filter parameters then do a filter, if filter value is None, then just ignore it.

def get_query_results(filter1=None, filter2=None, ...):
    res = models.Item.query
    if filter1 is not None:
        res = res.filter(filter1=filter1)
    if filter2 is not None:
        res = res.filter(filter2=filter2)
    ....
    return res.all()

What I want to avoid is the pattern

if XXX:
    res.filter(XXX=XXX)

I wonder if there is any more elegant way to achieve this?

For example, pass various filters as parameters?

Or maybe, we can do some magic to omit the filter when the filter value is None?

like image 257
Xuan Hu Avatar asked Jan 07 '15 02:01

Xuan Hu


People also ask

What is lazy dynamic SQLAlchemy?

lazy = 'dynamic': When querying with lazy = 'dynamic', however, a separate query gets generated for the related object. If you use the same query as 'select', it will return: You can see that it returns a sqlalchemy object instead of the city objects.

What is difference between filter and filter by in SQLAlchemy?

The second one, filter_by(), may be used only for filtering by something specifically stated - a string or some number value. So it's usable only for category filtering, not for expression filtering. On the other hand filter() allows using comparison expressions (==, <, >, etc.)

What does all () do in SQLAlchemy?

all() method. The Query object, when asked to return full entities, will deduplicate entries based on primary key, meaning if the same primary key value would appear in the results more than once, only one object of that primary key would be present.

Is SQLAlchemy between inclusive?

Using a SQL BETWEEN operator will evaluate ranges in an inclusive manner.


1 Answers

Code perfectly equivalent to the one you've shown is:

def get_query_results(*filters):
    res = models.Item.query
    for i, filt in enumerate(filters, 1):
        if filt is not None:
            d = {'filter{}'.format(i): filt}
            res = res.filter(**d)
    return res.all()

I'm not quite sure why you need the named argument to res.filter to be specifically filter1, filter2, etc, but this snippet will do it without the repetitious pattern that you understandably want to avoid.

Should the names not actually be filter1, filter2, etc, that's OK as long as the required names are known:

NAMES = 'foo bar baz bat'.split()

def get_query_results(*filters):
    res = models.Item.query
    for name, filt in zip(NAMES, filters):
        if filt is not None:
            d = {name: filt}
            res = res.filter(**d)
    return res.all()

This variant would work in this case.

like image 58
Alex Martelli Avatar answered Sep 17 '22 12:09

Alex Martelli