Reputation: 847
I need a function that does something like:
query = Member.query.filter(Member.age > 3)
query = filter_query(query)
def filter_query(query):
for q in query:
if not q.someAttr == "someValue":
query.remove(q) # <-- this doesn't work. is there anything similar?
return query
Please, don't ask why I need this. It's a long story )
UPDATE
I decided to use maybe not the most beautiful but still a solution that works for now.
query = Member.query.filter(Member.age > 3)
query = filter_query(query)
def filter_query(query):
id_list = []
for q in query:
if q.someAttr == "someValue":
id_list.append(q.id)
return Member.query.filter(Member.id.in_(id_list))
Upvotes: 0
Views: 1488
Reputation: 55669
The problem is that a query must be run in Sqlite which includes an ILIKE
filter on non-ASCII text, but the default Sqlite build does not support case-insensitive matching on such text.
One possible workaround is to store a case-normalised copy of the text in another column. This assumes that doubling the storage for the text is not an issue.
A simple way to do this would look like this:
class Likeable(Base):
__tablename__ = 'likeable'
id = sa.Column(sa.Integer, primary_key=True)
name = sa.Column(sa.String(32))
lowercase_name = sa.Column(sa.String(32))
Instances can be created like this:
likeable = Likeable(name='Россия', lowercase_name='россия')
and queried like this
session.query(Likeable).filter(Likeable.lowercase_name.like('р%'))
This is ok, but requires that name
and lowercase_name
are always updated simultaneously. We can get around this by masking lowercase_name
with a hybrid property, and intercepting assignments to name
with an attribute listener. The listener detects when name
is modified and passes the new value to lowercase_name
's setter.
import sqlalchemy as sa
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.ext.hybrid import hybrid_property
from sqlalchemy.orm import Session
Base = declarative_base()
class Likeable(Base):
__tablename__ = 'likeable'
id = sa.Column(sa.Integer, primary_key=True)
name = sa.Column(sa.String(32))
_lowercase_name = sa.Column(sa.String(32))
def __repr__(self):
return f"Likeable(name='{self.name}', lowercase_name='{self.lowercase_name}')"
@hybrid_property
def lowercase_name(self):
return self._lowercase_name
@lowercase_name.setter
def lowercase_name(self, value):
# str.casefold is preferred over str.lower for case-insensitive matching
# https://docs.python.org/3/library/stdtypes.html#str.casefold
self._lowercase_name = value.casefold()
@sa.event.listens_for(Likeable.name, 'set')
def receive_set(target, value, oldvalue, initiator):
target.lowercase_name = value
Running this code:
engine = sa.create_engine('sqlite:///', echo=True)
Base.metadata.create_all(bind=engine)
session = Session(bind=engine)
likeables = [Likeable(name=n) for n in ['Россия', 'Русский', 'Французский']]
session.add_all(likeables)
session.commit()
session.close()
session = Session(bind=engine)
q = session.query(Likeable).filter(Likeable.lowercase_name.like('р%'))
for r in q:
print(r)
session.close()
Produces this output:
Likeable(name='Россия', lowercase_name='россия')
Likeable(name='Русский', lowercase_name='русский')
This is demonstration code. For production, you would want to add checks to ensure that name
and lowercase_name
can't get out of sync.
Upvotes: 1