Coverage for phml\utilities\locate\index.py: 100%

30 statements  

« prev     ^ index     » next       coverage.py v6.5.0, created at 2023-04-05 15:06 -0500

1from typing import Any, Callable, overload 

2 

3from phml.nodes import MISSING, Element, Parent 

4from phml.utilities.validate.check import Test 

5 

6 

7class Index: 

8 """Uses the given key or key generator and creates a mutable dict of key value pairs 

9 that can be easily indexed. 

10 

11 Nodes that don't match the condition or don't have a valid key are not indexed. 

12 """ 

13 

14 indexed_tree: dict[Any, list[Element]] 

15 """The indexed collection of elements""" 

16 

17 def __init__( 

18 self, 

19 start: Parent, 

20 key: str | Callable[[Element], str], 

21 condition: Test | None = None, 

22 ) -> None: 

23 """ 

24 Args: 

25 `key` (str | Callable): Str represents the attribute to use as an index. Callable 

26 represents a function to call on each element to generate a key. The returned key 

27 must be able to be converted to a string. If none then element is skipped. 

28 `start` (Parent): The root or node to start at while indexing 

29 `test` (Test): The test to apply to each node. Only valid/passing nodes 

30 will be indexed 

31 """ 

32 from phml.utilities import ( # pylint: disable=import-outside-toplevel 

33 check, 

34 walk, 

35 ) 

36 

37 self.indexed_tree = {} 

38 self.key = key 

39 

40 for node in walk(start): 

41 if isinstance(node, Element): 

42 if condition is not None: 

43 if check(node, condition): 

44 self.add(node) 

45 else: 

46 self.add(node) 

47 

48 def __iter__(self): 

49 return iter(self.indexed_tree) 

50 

51 def __contains__(self, _key: str) -> bool: 

52 return _key in self.indexed_tree 

53 

54 def __str__(self): 

55 return str(self.indexed_tree) 

56 

57 def items(self): # pragma: no cover 

58 """Get the key value pairs of all indexes.""" 

59 return self.indexed_tree.items() 

60 

61 def values(self): # pragma: no cover 

62 """Get all the values in the collection.""" 

63 return self.indexed_tree.values() 

64 

65 def keys(self): # pragma: no cover 

66 """Get all the keys in the collection.""" 

67 return self.indexed_tree.keys() 

68 

69 def add(self, node: Element): 

70 """Adds element to indexed collection if not already there.""" 

71 

72 key = node.get(self.key, "") if isinstance(self.key, str) else self.key(node) 

73 if key not in self.indexed_tree: 

74 self.indexed_tree[key] = [node] 

75 

76 if node not in self.indexed_tree[key]: 

77 self.indexed_tree[key].append(node) 

78 

79 def remove(self, node: Element): 

80 """Removes element from indexed collection if there.""" 

81 

82 key = self.key if isinstance(self.key, str) else self.key(node) 

83 if key in self.indexed_tree and node in self.indexed_tree[key]: 

84 self.indexed_tree[key].remove(node) 

85 if len(self.indexed_tree[key]) == 0: 

86 self.indexed_tree.pop(key, None) 

87 

88 def __getitem__(self, key: Any) -> list[Element]: 

89 return self.indexed_tree[key] 

90 

91 @overload 

92 def get(self, _key: str, _default: Any = MISSING) -> list[Element] | Any: 

93 ... 

94 

95 @overload 

96 def get(self, _key: str) -> list[Element] | None: 

97 ... 

98 

99 def get( 

100 self, _key: str, _default: Any = MISSING 

101 ) -> list[Element] | None: # pragma: no cover 

102 """Get a specific index from the indexed tree.""" 

103 if _default != MISSING: 

104 return self.indexed_tree.get(_key, _default) 

105 return self.indexed_tree.get(_key, None) 

106 

107 # Built in key functions 

108 

109 @staticmethod 

110 def key_by_tag(node: Element) -> str: 

111 """Builds the key from an elements tag. If the node is not an element 

112 then the node's type is returned.""" 

113 

114 return node.tag