You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

tree.py 4.8 KiB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132
  1. """
  2. A class for storing a tree graph. Primarily used for filter constructs in the
  3. ORM.
  4. """
  5. import copy
  6. class Node(object):
  7. """
  8. A single internal node in the tree graph. A Node should be viewed as a
  9. connection (the root) with the children being either leaf nodes or other
  10. Node instances.
  11. """
  12. # Standard connector type. Clients usually won't use this at all and
  13. # subclasses will usually override the value.
  14. default = 'DEFAULT'
  15. def __init__(self, children=None, connector=None, negated=False):
  16. """
  17. Constructs a new Node. If no connector is given, the default will be
  18. used.
  19. """
  20. self.children = children[:] if children else []
  21. self.connector = connector or self.default
  22. self.negated = negated
  23. # We need this because of django.db.models.query_utils.Q. Q. __init__() is
  24. # problematic, but it is a natural Node subclass in all other respects.
  25. @classmethod
  26. def _new_instance(cls, children=None, connector=None, negated=False):
  27. """
  28. This is called to create a new instance of this class when we need new
  29. Nodes (or subclasses) in the internal code in this class. Normally, it
  30. just shadows __init__(). However, subclasses with an __init__ signature
  31. that is not an extension of Node.__init__ might need to implement this
  32. method to allow a Node to create a new instance of them (if they have
  33. any extra setting up to do).
  34. """
  35. obj = Node(children, connector, negated)
  36. obj.__class__ = cls
  37. return obj
  38. def __str__(self):
  39. if self.negated:
  40. return '(NOT (%s: %s))' % (self.connector, ', '.join(str(c) for c
  41. in self.children))
  42. return '(%s: %s)' % (self.connector, ', '.join(str(c) for c in
  43. self.children))
  44. def __repr__(self):
  45. return "<%s: %s>" % (self.__class__.__name__, self)
  46. def __deepcopy__(self, memodict):
  47. """
  48. Utility method used by copy.deepcopy().
  49. """
  50. obj = Node(connector=self.connector, negated=self.negated)
  51. obj.__class__ = self.__class__
  52. obj.children = copy.deepcopy(self.children, memodict)
  53. return obj
  54. def __len__(self):
  55. """
  56. The size of a node if the number of children it has.
  57. """
  58. return len(self.children)
  59. def __bool__(self):
  60. """
  61. For truth value testing.
  62. """
  63. return bool(self.children)
  64. def __nonzero__(self): # Python 2 compatibility
  65. return type(self).__bool__(self)
  66. def __contains__(self, other):
  67. """
  68. Returns True is 'other' is a direct child of this instance.
  69. """
  70. return other in self.children
  71. def add(self, data, conn_type, squash=True):
  72. """
  73. Combines this tree and the data represented by data using the
  74. connector conn_type. The combine is done by squashing the node other
  75. away if possible.
  76. This tree (self) will never be pushed to a child node of the
  77. combined tree, nor will the connector or negated properties change.
  78. The function returns a node which can be used in place of data
  79. regardless if the node other got squashed or not.
  80. If `squash` is False the data is prepared and added as a child to
  81. this tree without further logic.
  82. """
  83. if data in self.children:
  84. return data
  85. if not squash:
  86. self.children.append(data)
  87. return data
  88. if self.connector == conn_type:
  89. # We can reuse self.children to append or squash the node other.
  90. if (isinstance(data, Node) and not data.negated
  91. and (data.connector == conn_type or len(data) == 1)):
  92. # We can squash the other node's children directly into this
  93. # node. We are just doing (AB)(CD) == (ABCD) here, with the
  94. # addition that if the length of the other node is 1 the
  95. # connector doesn't matter. However, for the len(self) == 1
  96. # case we don't want to do the squashing, as it would alter
  97. # self.connector.
  98. self.children.extend(data.children)
  99. return self
  100. else:
  101. # We could use perhaps additional logic here to see if some
  102. # children could be used for pushdown here.
  103. self.children.append(data)
  104. return data
  105. else:
  106. obj = self._new_instance(self.children, self.connector,
  107. self.negated)
  108. self.connector = conn_type
  109. self.children = [obj, data]
  110. return data
  111. def negate(self):
  112. """
  113. Negate the sense of the root connector.
  114. """
  115. self.negated = not self.negated