import copy import re from mongoengine import Q from mongoengine.queryset.visitor import QueryCompilerVisitor, SimplificationVisitor, QCombination class RegexWrapper(object): def __init__(self, pattern, flags=None): super(RegexWrapper, self).__init__() self.pattern = pattern self.flags = flags @property def regex(self): return re.compile(self.pattern, self.flags if self.flags is not None else 0) class RegexMixin(object): def to_query(self, document): query = self.accept(SimplificationVisitor()) query = query.accept(RegexQueryCompilerVisitor(document)) return query def _combine(self, other, operation): """Combine this node with another node into a QCombination object. """ if getattr(other, 'empty', True): return self if self.empty: return other return RegexQCombination(operation, [self, other]) class RegexQCombination(RegexMixin, QCombination): pass class RegexQ(RegexMixin, Q): pass class RegexQueryCompilerVisitor(QueryCompilerVisitor): """ Improved mongoengine complied queries visitor class that supports compiled regex expressions as part of the query. We need this class since mongoengine's Q (QNode) class uses copy.deepcopy() as part of the tree simplification stage, which does not support re.compiled objects (since Python 2.5). This class allows users to provide regex strings wrapped in QueryRegex instances, which are lazily evaluated to to re.compile instances just before being visited for compilation (this is done after the simplification stage) """ def visit_query(self, query): query = copy.deepcopy(query) query.query = self._transform_query(query.query) return super(RegexQueryCompilerVisitor, self).visit_query(query) def _transform_query(self, query): return {k: v.regex if isinstance(v, RegexWrapper) else v for k, v in query.items()}