Changed the inheritance model to remove types
The inheritance model has changed, we no longer need to store an array of `types` with the model we can just use the classname in `_cls`. See the upgrade docs for information on how to upgrade MongoEngine/mongoengine#148
This commit is contained in:
		
							
								
								
									
										11
									
								
								mongoengine/queryset/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								mongoengine/queryset/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,11 @@ | ||||
| from mongoengine.errors import (DoesNotExist, MultipleObjectsReturned, | ||||
|                                 InvalidQueryError, OperationError, | ||||
|                                 NotUniqueError) | ||||
| from .field_list import * | ||||
| from .manager import * | ||||
| from .queryset import * | ||||
| from .transform import * | ||||
| from .visitor import * | ||||
|  | ||||
| __all__ = (field_list.__all__ + manager.__all__ + queryset.__all__ + | ||||
|            transform.__all__ + visitor.__all__) | ||||
							
								
								
									
										51
									
								
								mongoengine/queryset/field_list.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										51
									
								
								mongoengine/queryset/field_list.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,51 @@ | ||||
|  | ||||
| __all__ = ('QueryFieldList',) | ||||
|  | ||||
|  | ||||
| class QueryFieldList(object): | ||||
|     """Object that handles combinations of .only() and .exclude() calls""" | ||||
|     ONLY = 1 | ||||
|     EXCLUDE = 0 | ||||
|  | ||||
|     def __init__(self, fields=[], value=ONLY, always_include=[]): | ||||
|         self.value = value | ||||
|         self.fields = set(fields) | ||||
|         self.always_include = set(always_include) | ||||
|         self._id = None | ||||
|  | ||||
|     def __add__(self, f): | ||||
|         if not self.fields: | ||||
|             self.fields = f.fields | ||||
|             self.value = f.value | ||||
|         elif self.value is self.ONLY and f.value is self.ONLY: | ||||
|             self.fields = self.fields.intersection(f.fields) | ||||
|         elif self.value is self.EXCLUDE and f.value is self.EXCLUDE: | ||||
|             self.fields = self.fields.union(f.fields) | ||||
|         elif self.value is self.ONLY and f.value is self.EXCLUDE: | ||||
|             self.fields -= f.fields | ||||
|         elif self.value is self.EXCLUDE and f.value is self.ONLY: | ||||
|             self.value = self.ONLY | ||||
|             self.fields = f.fields - self.fields | ||||
|  | ||||
|         if '_id' in f.fields: | ||||
|             self._id = f.value | ||||
|  | ||||
|         if self.always_include: | ||||
|             if self.value is self.ONLY and self.fields: | ||||
|                 self.fields = self.fields.union(self.always_include) | ||||
|             else: | ||||
|                 self.fields -= self.always_include | ||||
|         return self | ||||
|  | ||||
|     def __nonzero__(self): | ||||
|         return bool(self.fields) | ||||
|  | ||||
|     def as_dict(self): | ||||
|         field_list = dict((field, self.value) for field in self.fields) | ||||
|         if self._id is not None: | ||||
|             field_list['_id'] = self._id | ||||
|         return field_list | ||||
|  | ||||
|     def reset(self): | ||||
|         self.fields = set([]) | ||||
|         self.value = self.ONLY | ||||
							
								
								
									
										61
									
								
								mongoengine/queryset/manager.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										61
									
								
								mongoengine/queryset/manager.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,61 @@ | ||||
| from functools import partial | ||||
| from .queryset import QuerySet | ||||
|  | ||||
| __all__ = ('queryset_manager', 'QuerySetManager') | ||||
|  | ||||
|  | ||||
| class QuerySetManager(object): | ||||
|     """ | ||||
|     The default QuerySet Manager. | ||||
|  | ||||
|     Custom QuerySet Manager functions can extend this class and users can | ||||
|     add extra queryset functionality.  Any custom manager methods must accept a | ||||
|     :class:`~mongoengine.Document` class as its first argument, and a | ||||
|     :class:`~mongoengine.queryset.QuerySet` as its second argument. | ||||
|  | ||||
|     The method function should return a :class:`~mongoengine.queryset.QuerySet` | ||||
|     , probably the same one that was passed in, but modified in some way. | ||||
|     """ | ||||
|  | ||||
|     get_queryset = None | ||||
|  | ||||
|     def __init__(self, queryset_func=None): | ||||
|         if queryset_func: | ||||
|             self.get_queryset = queryset_func | ||||
|         self._collections = {} | ||||
|  | ||||
|     def __get__(self, instance, owner): | ||||
|         """Descriptor for instantiating a new QuerySet object when | ||||
|         Document.objects is accessed. | ||||
|         """ | ||||
|         if instance is not None: | ||||
|             # Document class being used rather than a document object | ||||
|             return self | ||||
|  | ||||
|         # owner is the document that contains the QuerySetManager | ||||
|         queryset_class = owner._meta.get('queryset_class') or QuerySet | ||||
|         queryset = queryset_class(owner, owner._get_collection()) | ||||
|         if self.get_queryset: | ||||
|             arg_count = self.get_queryset.func_code.co_argcount | ||||
|             if arg_count == 1: | ||||
|                 queryset = self.get_queryset(queryset) | ||||
|             elif arg_count == 2: | ||||
|                 queryset = self.get_queryset(owner, queryset) | ||||
|             else: | ||||
|                 queryset = partial(self.get_queryset, owner, queryset) | ||||
|         return queryset | ||||
|  | ||||
|  | ||||
| def queryset_manager(func): | ||||
|     """Decorator that allows you to define custom QuerySet managers on | ||||
|     :class:`~mongoengine.Document` classes. The manager must be a function that | ||||
|     accepts a :class:`~mongoengine.Document` class as its first argument, and a | ||||
|     :class:`~mongoengine.queryset.QuerySet` as its second argument. The method | ||||
|     function should return a :class:`~mongoengine.queryset.QuerySet`, probably | ||||
|     the same one that was passed in, but modified in some way. | ||||
|     """ | ||||
|     if func.func_code.co_argcount == 1: | ||||
|         import warnings | ||||
|         msg = 'Methods decorated with queryset_manager should take 2 arguments' | ||||
|         warnings.warn(msg, DeprecationWarning) | ||||
|     return QuerySetManager(func) | ||||
							
								
								
									
										1257
									
								
								mongoengine/queryset/queryset.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1257
									
								
								mongoengine/queryset/queryset.py
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										237
									
								
								mongoengine/queryset/transform.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										237
									
								
								mongoengine/queryset/transform.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,237 @@ | ||||
| from collections import defaultdict | ||||
|  | ||||
| from mongoengine.common import _import_class | ||||
| from mongoengine.errors import InvalidQueryError, LookUpError | ||||
|  | ||||
| __all__ = ('query', 'update') | ||||
|  | ||||
|  | ||||
| COMPARISON_OPERATORS = ('ne', 'gt', 'gte', 'lt', 'lte', 'in', 'nin', 'mod', | ||||
|                           'all', 'size', 'exists', 'not') | ||||
| GEO_OPERATORS        = ('within_distance', 'within_spherical_distance', | ||||
|                         'within_box', 'within_polygon', 'near', 'near_sphere') | ||||
| STRING_OPERATORS     = ('contains', 'icontains', 'startswith', | ||||
|                         'istartswith', 'endswith', 'iendswith', | ||||
|                         'exact', 'iexact') | ||||
| CUSTOM_OPERATORS     = ('match',) | ||||
| MATCH_OPERATORS      = (COMPARISON_OPERATORS + GEO_OPERATORS + | ||||
|                         STRING_OPERATORS + CUSTOM_OPERATORS) | ||||
|  | ||||
| UPDATE_OPERATORS     = ('set', 'unset', 'inc', 'dec', 'pop', 'push', | ||||
|                         'push_all', 'pull', 'pull_all', 'add_to_set') | ||||
|  | ||||
|  | ||||
| def query(_doc_cls=None, _field_operation=False, **query): | ||||
|     """Transform a query from Django-style format to Mongo format. | ||||
|     """ | ||||
|     mongo_query = {} | ||||
|     merge_query = defaultdict(list) | ||||
|     for key, value in query.items(): | ||||
|         if key == "__raw__": | ||||
|             mongo_query.update(value) | ||||
|             continue | ||||
|  | ||||
|         parts = key.split('__') | ||||
|         indices = [(i, p) for i, p in enumerate(parts) if p.isdigit()] | ||||
|         parts = [part for part in parts if not part.isdigit()] | ||||
|         # Check for an operator and transform to mongo-style if there is | ||||
|         op = None | ||||
|         if parts[-1] in MATCH_OPERATORS: | ||||
|             op = parts.pop() | ||||
|  | ||||
|         negate = False | ||||
|         if parts[-1] == 'not': | ||||
|             parts.pop() | ||||
|             negate = True | ||||
|  | ||||
|         if _doc_cls: | ||||
|             # Switch field names to proper names [set in Field(name='foo')] | ||||
|             try: | ||||
|                 fields = _doc_cls._lookup_field(parts) | ||||
|             except Exception, e: | ||||
|                 raise InvalidQueryError(e) | ||||
|             parts = [] | ||||
|  | ||||
|             cleaned_fields = [] | ||||
|             for field in fields: | ||||
|                 append_field = True | ||||
|                 if isinstance(field, basestring): | ||||
|                     parts.append(field) | ||||
|                     append_field = False | ||||
|                 else: | ||||
|                     parts.append(field.db_field) | ||||
|                 if append_field: | ||||
|                     cleaned_fields.append(field) | ||||
|  | ||||
|             # Convert value to proper value | ||||
|             field = cleaned_fields[-1] | ||||
|  | ||||
|             singular_ops = [None, 'ne', 'gt', 'gte', 'lt', 'lte', 'not'] | ||||
|             singular_ops += STRING_OPERATORS | ||||
|             if op in singular_ops: | ||||
|                 if isinstance(field, basestring): | ||||
|                     if (op in STRING_OPERATORS and | ||||
|                         isinstance(value, basestring)): | ||||
|                         StringField = _import_class('StringField') | ||||
|                         value = StringField.prepare_query_value(op, value) | ||||
|                     else: | ||||
|                         value = field | ||||
|                 else: | ||||
|                     value = field.prepare_query_value(op, value) | ||||
|             elif op in ('in', 'nin', 'all', 'near'): | ||||
|                 # 'in', 'nin' and 'all' require a list of values | ||||
|                 value = [field.prepare_query_value(op, v) for v in value] | ||||
|  | ||||
|         # if op and op not in COMPARISON_OPERATORS: | ||||
|         if op: | ||||
|             if op in GEO_OPERATORS: | ||||
|                 if op == "within_distance": | ||||
|                     value = {'$within': {'$center': value}} | ||||
|                 elif op == "within_spherical_distance": | ||||
|                     value = {'$within': {'$centerSphere': value}} | ||||
|                 elif op == "within_polygon": | ||||
|                     value = {'$within': {'$polygon': value}} | ||||
|                 elif op == "near": | ||||
|                     value = {'$near': value} | ||||
|                 elif op == "near_sphere": | ||||
|                     value = {'$nearSphere': value} | ||||
|                 elif op == 'within_box': | ||||
|                     value = {'$within': {'$box': value}} | ||||
|                 else: | ||||
|                     raise NotImplementedError("Geo method '%s' has not " | ||||
|                                               "been implemented" % op) | ||||
|             elif op in CUSTOM_OPERATORS: | ||||
|                 if op == 'match': | ||||
|                     value = {"$elemMatch": value} | ||||
|                 else: | ||||
|                     NotImplementedError("Custom method '%s' has not " | ||||
|                                         "been implemented" % op) | ||||
|             elif op not in STRING_OPERATORS: | ||||
|                 value = {'$' + op: value} | ||||
|  | ||||
|         if negate: | ||||
|             value = {'$not': value} | ||||
|  | ||||
|         for i, part in indices: | ||||
|             parts.insert(i, part) | ||||
|         key = '.'.join(parts) | ||||
|         if op is None or key not in mongo_query: | ||||
|             mongo_query[key] = value | ||||
|         elif key in mongo_query: | ||||
|             if key in mongo_query and isinstance(mongo_query[key], dict): | ||||
|                 mongo_query[key].update(value) | ||||
|             else: | ||||
|                 # Store for manually merging later | ||||
|                 merge_query[key].append(value) | ||||
|  | ||||
|     # The queryset has been filter in such a way we must manually merge | ||||
|     for k, v in merge_query.items(): | ||||
|         merge_query[k].append(mongo_query[k]) | ||||
|         del mongo_query[k] | ||||
|         if isinstance(v, list): | ||||
|             value = [{k:val} for val in v] | ||||
|             if '$and' in mongo_query.keys(): | ||||
|                 mongo_query['$and'].append(value) | ||||
|             else: | ||||
|                 mongo_query['$and'] = value | ||||
|  | ||||
|     return mongo_query | ||||
|  | ||||
|  | ||||
| def update(_doc_cls=None, **update): | ||||
|     """Transform an update spec from Django-style format to Mongo format. | ||||
|     """ | ||||
|     mongo_update = {} | ||||
|     for key, value in update.items(): | ||||
|         if key == "__raw__": | ||||
|             mongo_update.update(value) | ||||
|             continue | ||||
|         parts = key.split('__') | ||||
|         # Check for an operator and transform to mongo-style if there is | ||||
|         op = None | ||||
|         if parts[0] in UPDATE_OPERATORS: | ||||
|             op = parts.pop(0) | ||||
|             # Convert Pythonic names to Mongo equivalents | ||||
|             if op in ('push_all', 'pull_all'): | ||||
|                 op = op.replace('_all', 'All') | ||||
|             elif op == 'dec': | ||||
|                 # Support decrement by flipping a positive value's sign | ||||
|                 # and using 'inc' | ||||
|                 op = 'inc' | ||||
|                 if value > 0: | ||||
|                     value = -value | ||||
|             elif op == 'add_to_set': | ||||
|                 op = op.replace('_to_set', 'ToSet') | ||||
|  | ||||
|         match = None | ||||
|         if parts[-1] in COMPARISON_OPERATORS: | ||||
|             match = parts.pop() | ||||
|  | ||||
|         if _doc_cls: | ||||
|             # Switch field names to proper names [set in Field(name='foo')] | ||||
|             try: | ||||
|                 fields = _doc_cls._lookup_field(parts) | ||||
|             except Exception, e: | ||||
|                 raise InvalidQueryError(e) | ||||
|             parts = [] | ||||
|  | ||||
|             cleaned_fields = [] | ||||
|             for field in fields: | ||||
|                 append_field = True | ||||
|                 if isinstance(field, basestring): | ||||
|                     # Convert the S operator to $ | ||||
|                     if field == 'S': | ||||
|                         field = '$' | ||||
|                     parts.append(field) | ||||
|                     append_field = False | ||||
|                 else: | ||||
|                     parts.append(field.db_field) | ||||
|                 if append_field: | ||||
|                     cleaned_fields.append(field) | ||||
|  | ||||
|             # Convert value to proper value | ||||
|             field = cleaned_fields[-1] | ||||
|  | ||||
|             if op in (None, 'set', 'push', 'pull'): | ||||
|                 if field.required or value is not None: | ||||
|                     value = field.prepare_query_value(op, value) | ||||
|             elif op in ('pushAll', 'pullAll'): | ||||
|                 value = [field.prepare_query_value(op, v) for v in value] | ||||
|             elif op == 'addToSet': | ||||
|                 if isinstance(value, (list, tuple, set)): | ||||
|                     value = [field.prepare_query_value(op, v) for v in value] | ||||
|                 elif field.required or value is not None: | ||||
|                     value = field.prepare_query_value(op, value) | ||||
|  | ||||
|         if match: | ||||
|             match = '$' + match | ||||
|             value = {match: value} | ||||
|  | ||||
|         key = '.'.join(parts) | ||||
|  | ||||
|         if not op: | ||||
|             raise InvalidQueryError("Updates must supply an operation " | ||||
|                                     "eg: set__FIELD=value") | ||||
|  | ||||
|         if 'pull' in op and '.' in key: | ||||
|             # Dot operators don't work on pull operations | ||||
|             # it uses nested dict syntax | ||||
|             if op == 'pullAll': | ||||
|                 raise InvalidQueryError("pullAll operations only support " | ||||
|                                         "a single field depth") | ||||
|  | ||||
|             parts.reverse() | ||||
|             for key in parts: | ||||
|                 value = {key: value} | ||||
|         elif op == 'addToSet' and isinstance(value, list): | ||||
|             value = {key: {"$each": value}} | ||||
|         else: | ||||
|             value = {key: value} | ||||
|         key = '$' + op | ||||
|  | ||||
|         if key not in mongo_update: | ||||
|             mongo_update[key] = value | ||||
|         elif key in mongo_update and isinstance(mongo_update[key], dict): | ||||
|             mongo_update[key].update(value) | ||||
|  | ||||
|     return mongo_update | ||||
							
								
								
									
										237
									
								
								mongoengine/queryset/visitor.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										237
									
								
								mongoengine/queryset/visitor.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,237 @@ | ||||
| import copy | ||||
|  | ||||
| from mongoengine.errors import InvalidQueryError | ||||
| from mongoengine.python_support import product, reduce | ||||
|  | ||||
| from mongoengine.queryset import transform | ||||
|  | ||||
| __all__ = ('Q',) | ||||
|  | ||||
|  | ||||
| class QNodeVisitor(object): | ||||
|     """Base visitor class for visiting Q-object nodes in a query tree. | ||||
|     """ | ||||
|  | ||||
|     def visit_combination(self, combination): | ||||
|         """Called by QCombination objects. | ||||
|         """ | ||||
|         return combination | ||||
|  | ||||
|     def visit_query(self, query): | ||||
|         """Called by (New)Q objects. | ||||
|         """ | ||||
|         return query | ||||
|  | ||||
|  | ||||
| class SimplificationVisitor(QNodeVisitor): | ||||
|     """Simplifies query trees by combinging unnecessary 'and' connection nodes | ||||
|     into a single Q-object. | ||||
|     """ | ||||
|  | ||||
|     def visit_combination(self, combination): | ||||
|         if combination.operation == combination.AND: | ||||
|             # The simplification only applies to 'simple' queries | ||||
|             if all(isinstance(node, Q) for node in combination.children): | ||||
|                 queries = [node.query for node in combination.children] | ||||
|                 return Q(**self._query_conjunction(queries)) | ||||
|         return combination | ||||
|  | ||||
|     def _query_conjunction(self, queries): | ||||
|         """Merges query dicts - effectively &ing them together. | ||||
|         """ | ||||
|         query_ops = set() | ||||
|         combined_query = {} | ||||
|         for query in queries: | ||||
|             ops = set(query.keys()) | ||||
|             # Make sure that the same operation isn't applied more than once | ||||
|             # to a single field | ||||
|             intersection = ops.intersection(query_ops) | ||||
|             if intersection: | ||||
|                 msg = 'Duplicate query conditions: ' | ||||
|                 raise InvalidQueryError(msg + ', '.join(intersection)) | ||||
|  | ||||
|             query_ops.update(ops) | ||||
|             combined_query.update(copy.deepcopy(query)) | ||||
|         return combined_query | ||||
|  | ||||
|  | ||||
| class QueryTreeTransformerVisitor(QNodeVisitor): | ||||
|     """Transforms the query tree in to a form that may be used with MongoDB. | ||||
|     """ | ||||
|  | ||||
|     def visit_combination(self, combination): | ||||
|         if combination.operation == combination.AND: | ||||
|             # MongoDB doesn't allow us to have too many $or operations in our | ||||
|             # queries, so the aim is to move the ORs up the tree to one | ||||
|             # 'master' $or. Firstly, we must find all the necessary parts (part | ||||
|             # of an AND combination or just standard Q object), and store them | ||||
|             # separately from the OR parts. | ||||
|             or_groups = [] | ||||
|             and_parts = [] | ||||
|             for node in combination.children: | ||||
|                 if isinstance(node, QCombination): | ||||
|                     if node.operation == node.OR: | ||||
|                         # Any of the children in an $or component may cause | ||||
|                         # the query to succeed | ||||
|                         or_groups.append(node.children) | ||||
|                     elif node.operation == node.AND: | ||||
|                         and_parts.append(node) | ||||
|                 elif isinstance(node, Q): | ||||
|                     and_parts.append(node) | ||||
|  | ||||
|             # Now we combine the parts into a usable query. AND together all of | ||||
|             # the necessary parts. Then for each $or part, create a new query | ||||
|             # that ANDs the necessary part with the $or part. | ||||
|             clauses = [] | ||||
|             for or_group in product(*or_groups): | ||||
|                 q_object = reduce(lambda a, b: a & b, and_parts, Q()) | ||||
|                 q_object = reduce(lambda a, b: a & b, or_group, q_object) | ||||
|                 clauses.append(q_object) | ||||
|             # Finally, $or the generated clauses in to one query. Each of the | ||||
|             # clauses is sufficient for the query to succeed. | ||||
|             return reduce(lambda a, b: a | b, clauses, Q()) | ||||
|  | ||||
|         if combination.operation == combination.OR: | ||||
|             children = [] | ||||
|             # Crush any nested ORs in to this combination as MongoDB doesn't | ||||
|             # support nested $or operations | ||||
|             for node in combination.children: | ||||
|                 if (isinstance(node, QCombination) and | ||||
|                     node.operation == combination.OR): | ||||
|                     children += node.children | ||||
|                 else: | ||||
|                     children.append(node) | ||||
|             combination.children = children | ||||
|  | ||||
|         return combination | ||||
|  | ||||
|  | ||||
| class QueryCompilerVisitor(QNodeVisitor): | ||||
|     """Compiles the nodes in a query tree to a PyMongo-compatible query | ||||
|     dictionary. | ||||
|     """ | ||||
|  | ||||
|     def __init__(self, document): | ||||
|         self.document = document | ||||
|  | ||||
|     def visit_combination(self, combination): | ||||
|         if combination.operation == combination.OR: | ||||
|             return {'$or': combination.children} | ||||
|         elif combination.operation == combination.AND: | ||||
|             return self._mongo_query_conjunction(combination.children) | ||||
|         return combination | ||||
|  | ||||
|     def visit_query(self, query): | ||||
|         return transform.query(self.document, **query.query) | ||||
|  | ||||
|     def _mongo_query_conjunction(self, queries): | ||||
|         """Merges Mongo query dicts - effectively &ing them together. | ||||
|         """ | ||||
|         combined_query = {} | ||||
|         for query in queries: | ||||
|             for field, ops in query.items(): | ||||
|                 if field not in combined_query: | ||||
|                     combined_query[field] = ops | ||||
|                 else: | ||||
|                     # The field is already present in the query the only way | ||||
|                     # we can merge is if both the existing value and the new | ||||
|                     # value are operation dicts, reject anything else | ||||
|                     if (not isinstance(combined_query[field], dict) or | ||||
|                         not isinstance(ops, dict)): | ||||
|                         message = 'Conflicting values for ' + field | ||||
|                         raise InvalidQueryError(message) | ||||
|  | ||||
|                     current_ops = set(combined_query[field].keys()) | ||||
|                     new_ops = set(ops.keys()) | ||||
|                     # Make sure that the same operation isn't applied more than | ||||
|                     # once to a single field | ||||
|                     intersection = current_ops.intersection(new_ops) | ||||
|                     if intersection: | ||||
|                         msg = 'Duplicate query conditions: ' | ||||
|                         raise InvalidQueryError(msg + ', '.join(intersection)) | ||||
|  | ||||
|                     # Right! We've got two non-overlapping dicts of operations! | ||||
|                     combined_query[field].update(copy.deepcopy(ops)) | ||||
|         return combined_query | ||||
|  | ||||
|  | ||||
| class QNode(object): | ||||
|     """Base class for nodes in query trees. | ||||
|     """ | ||||
|  | ||||
|     AND = 0 | ||||
|     OR = 1 | ||||
|  | ||||
|     def to_query(self, document): | ||||
|         query = self.accept(SimplificationVisitor()) | ||||
|         query = query.accept(QueryTreeTransformerVisitor()) | ||||
|         query = query.accept(QueryCompilerVisitor(document)) | ||||
|         return query | ||||
|  | ||||
|     def accept(self, visitor): | ||||
|         raise NotImplementedError | ||||
|  | ||||
|     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 QCombination(operation, [self, other]) | ||||
|  | ||||
|     @property | ||||
|     def empty(self): | ||||
|         return False | ||||
|  | ||||
|     def __or__(self, other): | ||||
|         return self._combine(other, self.OR) | ||||
|  | ||||
|     def __and__(self, other): | ||||
|         return self._combine(other, self.AND) | ||||
|  | ||||
|  | ||||
| class QCombination(QNode): | ||||
|     """Represents the combination of several conditions by a given logical | ||||
|     operator. | ||||
|     """ | ||||
|  | ||||
|     def __init__(self, operation, children): | ||||
|         self.operation = operation | ||||
|         self.children = [] | ||||
|         for node in children: | ||||
|             # If the child is a combination of the same type, we can merge its | ||||
|             # children directly into this combinations children | ||||
|             if isinstance(node, QCombination) and node.operation == operation: | ||||
|                 self.children += node.children | ||||
|             else: | ||||
|                 self.children.append(node) | ||||
|  | ||||
|     def accept(self, visitor): | ||||
|         for i in range(len(self.children)): | ||||
|             if isinstance(self.children[i], QNode): | ||||
|                 self.children[i] = self.children[i].accept(visitor) | ||||
|  | ||||
|         return visitor.visit_combination(self) | ||||
|  | ||||
|     @property | ||||
|     def empty(self): | ||||
|         return not bool(self.children) | ||||
|  | ||||
|  | ||||
| class Q(QNode): | ||||
|     """A simple query object, used in a query tree to build up more complex | ||||
|     query structures. | ||||
|     """ | ||||
|  | ||||
|     def __init__(self, **query): | ||||
|         self.query = query | ||||
|  | ||||
|     def accept(self, visitor): | ||||
|         return visitor.visit_query(self) | ||||
|  | ||||
|     @property | ||||
|     def empty(self): | ||||
|         return not bool(self.query) | ||||
		Reference in New Issue
	
	Block a user