Coverage for /var/srv/projects/api.amasfac.comuna18.com/tmp/venv/lib/python3.9/site-packages/django/utils/tree.py: 82%
43 statements
« prev ^ index » next coverage.py v6.4.4, created at 2023-07-17 14:22 -0600
« prev ^ index » next coverage.py v6.4.4, created at 2023-07-17 14:22 -0600
1"""
2A class for storing a tree graph. Primarily used for filter constructs in the
3ORM.
4"""
6import copy
8from django.utils.hashable import make_hashable
11class Node:
12 """
13 A single internal node in the tree graph. A Node should be viewed as a
14 connection (the root) with the children being either leaf nodes or other
15 Node instances.
16 """
18 # Standard connector type. Clients usually won't use this at all and
19 # subclasses will usually override the value.
20 default = "DEFAULT"
22 def __init__(self, children=None, connector=None, negated=False):
23 """Construct a new Node. If no connector is given, use the default."""
24 self.children = children[:] if children else []
25 self.connector = connector or self.default
26 self.negated = negated
28 # Required because django.db.models.query_utils.Q. Q. __init__() is
29 # problematic, but it is a natural Node subclass in all other respects.
30 @classmethod
31 def _new_instance(cls, children=None, connector=None, negated=False):
32 """
33 Create a new instance of this class when new Nodes (or subclasses) are
34 needed in the internal code in this class. Normally, it just shadows
35 __init__(). However, subclasses with an __init__ signature that aren't
36 an extension of Node.__init__ might need to implement this method to
37 allow a Node to create a new instance of them (if they have any extra
38 setting up to do).
39 """
40 obj = Node(children, connector, negated)
41 obj.__class__ = cls
42 return obj
44 def __str__(self):
45 template = "(NOT (%s: %s))" if self.negated else "(%s: %s)"
46 return template % (self.connector, ", ".join(str(c) for c in self.children))
48 def __repr__(self):
49 return "<%s: %s>" % (self.__class__.__name__, self)
51 def __deepcopy__(self, memodict):
52 obj = Node(connector=self.connector, negated=self.negated)
53 obj.__class__ = self.__class__
54 obj.children = copy.deepcopy(self.children, memodict)
55 return obj
57 def __len__(self):
58 """Return the number of children this node has."""
59 return len(self.children)
61 def __bool__(self):
62 """Return whether or not this node has children."""
63 return bool(self.children)
65 def __contains__(self, other):
66 """Return True if 'other' is a direct child of this instance."""
67 return other in self.children
69 def __eq__(self, other):
70 return (
71 self.__class__ == other.__class__
72 and self.connector == other.connector
73 and self.negated == other.negated
74 and self.children == other.children
75 )
77 def __hash__(self):
78 return hash(
79 (
80 self.__class__,
81 self.connector,
82 self.negated,
83 *make_hashable(self.children),
84 )
85 )
87 def add(self, data, conn_type):
88 """
89 Combine this tree and the data represented by data using the
90 connector conn_type. The combine is done by squashing the node other
91 away if possible.
93 This tree (self) will never be pushed to a child node of the
94 combined tree, nor will the connector or negated properties change.
96 Return a node which can be used in place of data regardless if the
97 node other got squashed or not.
98 """
99 if self.connector != conn_type: 99 ↛ 100line 99 didn't jump to line 100, because the condition on line 99 was never true
100 obj = self._new_instance(self.children, self.connector, self.negated)
101 self.connector = conn_type
102 self.children = [obj, data]
103 return data
104 elif (
105 isinstance(data, Node)
106 and not data.negated
107 and (data.connector == conn_type or len(data) == 1)
108 ):
109 # We can squash the other node's children directly into this node.
110 # We are just doing (AB)(CD) == (ABCD) here, with the addition that
111 # if the length of the other node is 1 the connector doesn't
112 # matter. However, for the len(self) == 1 case we don't want to do
113 # the squashing, as it would alter self.connector.
114 self.children.extend(data.children)
115 return self
116 else:
117 # We could use perhaps additional logic here to see if some
118 # children could be used for pushdown here.
119 self.children.append(data)
120 return data
122 def negate(self):
123 """Negate the sense of the root connector."""
124 self.negated = not self.negated