3

I have a database which I need to return any value that matches the query or looks similar.

Using flask-sqlalchemy I can filter manually, but I'm having trouble getting a list of objects using list comprehensions or any other more pythonic method.

I have already tried to create a dict with all model columns (which I have a list ) and the search value passed to the filter_by query

column_dict_and_value = {column_name:'value' for column_name in columns}    
a = model.query.filter_by(**column_dict_and_value).all()

...but doesn't even return an existing object, although

a = model.query.filter_by(column_name='value').all()

actually returns the object.

I tried filter(.like('%')) and it sort of works

a = model.query.filter(model.column_name.like('valu%')).all()

returns me a list of all the objects that matches that pattern but for that column, and I want to iterate over all columns. Basically a full blown search since I'm not sure what the user is going to look for and I want to show the object if the query exist on any column or a list of objects if the query is incomplete, like the * in any search.

I tried to use list comprehensions to iterate through the model attributes but I'm not allowed to do that apparently:

a = [model.query.filter(model.column.like('valu%')) for column in columns]

...this complains that column is not an attribute of the model, which makes sense due to the syntax, but I had to try anyway, innit?

I'm a newbie regarding databases and class objects so please be gentle. I tried to look for similar answers but I can't find the one that suits me. the filter_by(one_column, second_column, etc) doesn't look to me very pythonic and if for any reason I change the model i need to change the query too where as creating a dict comprehension of the model seems to me more foolproof.

SOLUTION

Based on calestini proposed answer I wrote this code that seems to do the trick. It also cleans the list because if all() doesn't have any result (since it looks in every field) it returns None and adds up to the list. I'd rather prefer it clean.

Note: All my fields are text. Please check the third proposed solution from calestini if yours differ. Not tested it though.

columns = [
"foo",
"bar",
"foobar"
]


def list_everything(search):
    d = {column: search for column in columns}
    raw = [
        model.query.filter(getattr(model, col).ilike(f"{val}%")).all()
        for col, val in d.items()
    ]
    return [item for item in raw if item]

I'll keep optimizing and update this code if I come with a better solution. Thanks a lot

Jose Rodriguez
  • 157
  • 2
  • 8
  • 1
    May not be a dupe but at least this can get you in the right direction https://stackoverflow.com/questions/7942547/using-or-in-sqlalchemy. Also, have not tried this yet but found an interesting link for filters https://pypi.org/project/sqlalchemy-filters/ – mad_ Jul 24 '19 at 16:09

1 Answers1

10

Probably the reason why you are not getting any result from

a = model.query.filter_by(**column_dict_and_value).all()

is because filter_by is testing for direct equality. In your case you are looking for a pattern, so you will need to loop and use filter as opposed to filter_by.

Assuming all your columns are String or Text types, you can try the following:

a = model.query

for col, val in column_dict_and_value.items():
   a = a.filter(getattr(model, col).ilike(f'{val}%')) #ilike for case insensitive

a = a.all()

Or vs And

The issue can also be that in your case you could be testing for intersection but expecting union. In other words, you are returning something only if the pattern matches in all columns. If instead you want a result in case any column matches, then you need to tweak the code a bit:

condition = or_(*[getattr(model, col).ilike(f'{val}%') for col, val in column_dict_and_value.items()])

a = model.query.filter(codition).all()

Now if you actually have other data types among the columns, then you can try to first verify if the column type and then pass the same logic:

for col, val in column_dict_and_value.items():
    ## check if field python equivalent is string
    if isinstance(class_.__table__.c[col].type.python_type, str):
       ...
realr
  • 3,652
  • 6
  • 23
  • 34
  • 1
    I can't make your code to work fine, the first one doesn't find anything aswell and the second "condition" throws a `ValueError: too many values to unpack (expected 2)`. Haven't tried the third way since all my fields are strings since i don't need to operate with the data, just show it. I feel like I'm getting there but it's frustrating... – Jose Rodriguez Jul 25 '19 at 10:47
  • I'm not marking it as accepted since I can't make run your code and I'm not sure it is correct. It might be though. Please double check and I'l accept. – Jose Rodriguez Jul 25 '19 at 12:33
  • Apologize @jose, I haven't actually run your code, let me try to run a flask app and correct the answer. Thanks for pointing it out. – realr Jul 25 '19 at 12:53
  • @JoseRodriguez Fixed the line, it was missing the `items()` from to unpack the dictionary. – realr Jul 25 '19 at 16:11