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

1""" 

2A class for storing a tree graph. Primarily used for filter constructs in the 

3ORM. 

4""" 

5 

6import copy 

7 

8from django.utils.hashable import make_hashable 

9 

10 

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 """ 

17 

18 # Standard connector type. Clients usually won't use this at all and 

19 # subclasses will usually override the value. 

20 default = "DEFAULT" 

21 

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 

27 

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 

43 

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)) 

47 

48 def __repr__(self): 

49 return "<%s: %s>" % (self.__class__.__name__, self) 

50 

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 

56 

57 def __len__(self): 

58 """Return the number of children this node has.""" 

59 return len(self.children) 

60 

61 def __bool__(self): 

62 """Return whether or not this node has children.""" 

63 return bool(self.children) 

64 

65 def __contains__(self, other): 

66 """Return True if 'other' is a direct child of this instance.""" 

67 return other in self.children 

68 

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 ) 

76 

77 def __hash__(self): 

78 return hash( 

79 ( 

80 self.__class__, 

81 self.connector, 

82 self.negated, 

83 *make_hashable(self.children), 

84 ) 

85 ) 

86 

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. 

92 

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. 

95 

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 

121 

122 def negate(self): 

123 """Negate the sense of the root connector.""" 

124 self.negated = not self.negated