"""A class for storing a tree graph. Primarily used for filter constructs in theORM."""importcopy
[docs]classNode(object):""" A single internal node in the tree graph. A Node should be viewed as a connection (the root) with the children being either leaf nodes or other Node instances. """# Standard connector type. Clients usually won't use this at all and# subclasses will usually override the value.default='DEFAULT'def__init__(self,children=None,connector=None,negated=False):""" Constructs a new Node. If no connector is given, the default will be used. """self.children=children[:]ifchildrenelse[]self.connector=connectororself.defaultself.negated=negated# We need this because of django.db.models.query_utils.Q. Q. __init__() is# problematic, but it is a natural Node subclass in all other respects.@classmethoddef_new_instance(cls,children=None,connector=None,negated=False):""" This is called to create a new instance of this class when we need new Nodes (or subclasses) in the internal code in this class. Normally, it just shadows __init__(). However, subclasses with an __init__ signature that is not an extension of Node.__init__ might need to implement this method to allow a Node to create a new instance of them (if they have any extra setting up to do). """obj=Node(children,connector,negated)obj.__class__=clsreturnobjdef__str__(self):ifself.negated:return'(NOT (%s: %s))'%(self.connector,', '.join(str(c)forcinself.children))return'(%s: %s)'%(self.connector,', '.join(str(c)forcinself.children))def__repr__(self):return"<%s: %s>"%(self.__class__.__name__,self)def__deepcopy__(self,memodict):""" Utility method used by copy.deepcopy(). """obj=Node(connector=self.connector,negated=self.negated)obj.__class__=self.__class__obj.children=copy.deepcopy(self.children,memodict)returnobjdef__len__(self):""" The size of a node if the number of children it has. """returnlen(self.children)def__bool__(self):""" For truth value testing. """returnbool(self.children)def__nonzero__(self):# Python 2 compatibilityreturntype(self).__bool__(self)def__contains__(self,other):""" Returns True is 'other' is a direct child of this instance. """returnotherinself.children
[docs]defadd(self,data,conn_type,squash=True):""" Combines this tree and the data represented by data using the connector conn_type. The combine is done by squashing the node other away if possible. This tree (self) will never be pushed to a child node of the combined tree, nor will the connector or negated properties change. The function returns a node which can be used in place of data regardless if the node other got squashed or not. If `squash` is False the data is prepared and added as a child to this tree without further logic. """ifdatainself.children:returndataifnotsquash:self.children.append(data)returndataifself.connector==conn_type:# We can reuse self.children to append or squash the node other.if(isinstance(data,Node)andnotdata.negatedand(data.connector==conn_typeorlen(data)==1)):# We can squash the other node's children directly into this# node. We are just doing (AB)(CD) == (ABCD) here, with the# addition that if the length of the other node is 1 the# connector doesn't matter. However, for the len(self) == 1# case we don't want to do the squashing, as it would alter# self.connector.self.children.extend(data.children)returnselfelse:# We could use perhaps additional logic here to see if some# children could be used for pushdown here.self.children.append(data)returndataelse:obj=self._new_instance(self.children,self.connector,self.negated)self.connector=conn_typeself.children=[obj,data]returndata
[docs]defnegate(self):""" Negate the sense of the root connector. """self.negated=notself.negated