# inequality_mixin.py from google.appengine.ext import db import re class MultiInequalityMixin(object): """ Allows multiple inequality matches in a query. Doesn't apply to GQL queries (yet) as I don't know how those work. """ @classmethod def all(cls, **kwds): return MultiInequalityQuery(cls, **kwds) @classmethod def gql(cls, query_string, *args, **kwds): raise NotImplementedError("MultiInequalityGqlQuery isn't implemented yet") #return MultiInequalityGqlQuery(cls, query_string, *args, **kwds) # matches db._FILTER_REGEX, I hope. Well, except for 'in' and plain '==?', # which aren't inequalities. prop_op_regex = re.compile(r'^\s*([^\s]+)\s+([<>]=?|!=)\s*$') class MultiInequalityQuery(db.Query): """ Derives from db.Query, if you request inequalities on multiple properties (normally not allowed by appengine) it does the first the usual way but then keeps the rest aside and 'post-filters' them. """ def __init__(self, *args, **kwargs): self.ineq_prop = None self.ineq_post = [] return super(MultiInequalityQuery, self).__init__(*args, **kwargs) def filter(self, prop_op, value): """ pass through most queries, except inequalities where this wouldn't be allowed. """ def make_closure(prop, op, value): if (op == '>'): return lambda x: getattr(x, prop) > value elif (op == '<'): return lambda x: getattr(x, prop) < value elif (op == '>='): return lambda x: getattr(x, prop) >= value elif (op == '<='): return lambda x: getattr(x, prop) <= value elif (op == '!='): return lambda x: getattr(x, prop) != value else: raise NotImplementedError("Unknown op %s" % op) prop_op_match = prop_op_regex.match(prop_op) if prop_op_match: prop, op = prop_op_match.groups() if self.ineq_prop not in (prop, None): self.ineq_post.append(make_closure(prop, op, value)) return self self.ineq_prop = prop super(MultiInequalityQuery, self).filter(prop_op, value) return self def __iter__(self): """ Chain onto the Query.__iter__ but reject objects not passing all the postfilters """ for x in super(MultiInequalityQuery, self).__iter__(): try: if all([ f(x) for f in self.ineq_post ]): yield x except AttributeError: pass def count(self, limit=None): """ Count the number of items out of the iterator. Inefficient, but hey. """ if not self.ineq_post: return super(MultiInequalityQuery, self).count(limit) c = 0 for x in self: c = c + 1 if limit is not None and c >= limit: break return c def fetch(self, limit, offset=0): """ Fetch a list of items. Unlike Query.fetch, this just calls the iterator (a lot) so is unlikely to be fabulously efficient """ if not self.ineq_post: return super(MultiInequalityQuery, self).fetch(limit, offset) c = 0 things = [] for x in self: if c >= offset: things.append(x) if c >= offset + limit: break return things # __getitem__ and get are already implemented in terms of fetch # so that should be okay.