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.8KB

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