Coverage for src/hdmf/query.py: 69%

120 statements  

« prev     ^ index     » next       coverage.py v7.2.5, created at 2023-08-18 20:49 +0000

1from abc import ABCMeta, abstractmethod 

2 

3import numpy as np 

4 

5from .array import Array 

6from .utils import ExtenderMeta, docval_macro, docval, getargs 

7 

8 

9class Query(metaclass=ExtenderMeta): 

10 __operations__ = ( 

11 '__lt__', 

12 '__gt__', 

13 '__le__', 

14 '__ge__', 

15 '__eq__', 

16 '__ne__', 

17 ) 

18 

19 @classmethod 

20 def __build_operation(cls, op): 

21 def __func(self, arg): 

22 return cls(self, op, arg) 

23 

24 @ExtenderMeta.pre_init 

25 def __make_operators(cls, name, bases, classdict): 

26 if not isinstance(cls.__operations__, tuple): 26 ↛ 27line 26 didn't jump to line 27, because the condition on line 26 was never true

27 raise TypeError("'__operations__' must be of type tuple") 

28 # add any new operations 

29 if len(bases) and 'Query' in globals() and issubclass(bases[-1], Query) \ 29 ↛ 31line 29 didn't jump to line 31, because the condition on line 29 was never true

30 and bases[-1].__operations__ is not cls.__operations__: 

31 new_operations = list(cls.__operations__) 

32 new_operations[0:0] = bases[-1].__operations__ 

33 cls.__operations__ = tuple(new_operations) 

34 for op in cls.__operations__: 

35 if not hasattr(cls, op): 35 ↛ 36line 35 didn't jump to line 36, because the condition on line 35 was never true

36 setattr(cls, op, cls.__build_operation(op)) 

37 

38 def __init__(self, obj, op, arg): 

39 self.obj = obj 

40 self.op = op 

41 self.arg = arg 

42 self.collapsed = None 

43 self.expanded = None 

44 

45 @docval({'name': 'expand', 'type': bool, 'help': 'whether or not to expand result', 'default': True}) 

46 def evaluate(self, **kwargs): 

47 expand = getargs('expand', kwargs) 

48 if expand: 48 ↛ 53line 48 didn't jump to line 53, because the condition on line 48 was never false

49 if self.expanded is None: 49 ↛ 51line 49 didn't jump to line 51, because the condition on line 49 was never false

50 self.expanded = self.__evalhelper() 

51 return self.expanded 

52 else: 

53 if self.collapsed is None: 

54 self.collapsed = self.__collapse(self.__evalhelper()) 

55 return self.collapsed 

56 

57 def __evalhelper(self): 

58 obj = self.obj 

59 arg = self.arg 

60 if isinstance(obj, Query): 60 ↛ 61line 60 didn't jump to line 61, because the condition on line 60 was never true

61 obj = obj.evaluate() 

62 elif isinstance(obj, HDMFDataset): 62 ↛ 64line 62 didn't jump to line 64, because the condition on line 62 was never false

63 obj = obj.dataset 

64 if isinstance(arg, Query): 64 ↛ 65line 64 didn't jump to line 65, because the condition on line 64 was never true

65 arg = self.arg.evaluate() 

66 return getattr(obj, self.op)(self.arg) 

67 

68 def __collapse(self, result): 

69 if isinstance(result, slice): 

70 return (result.start, result.stop) 

71 elif isinstance(result, list): 

72 ret = list() 

73 for idx in result: 

74 if isinstance(idx, slice) and (idx.step is None or idx.step == 1): 

75 ret.append((idx.start, idx.stop)) 

76 else: 

77 ret.append(idx) 

78 return ret 

79 else: 

80 return result 

81 

82 def __and__(self, other): 

83 return NotImplemented 

84 

85 def __or__(self, other): 

86 return NotImplemented 

87 

88 def __xor__(self, other): 

89 return NotImplemented 

90 

91 def __contains__(self, other): 

92 return NotImplemented 

93 

94 

95@docval_macro('array_data') 

96class HDMFDataset(metaclass=ExtenderMeta): 

97 __operations__ = ( 

98 '__lt__', 

99 '__gt__', 

100 '__le__', 

101 '__ge__', 

102 '__eq__', 

103 '__ne__', 

104 ) 

105 

106 @classmethod 

107 def __build_operation(cls, op): 

108 def __func(self, arg): 

109 return Query(self, op, arg) 

110 

111 setattr(__func, '__name__', op) 

112 return __func 

113 

114 @ExtenderMeta.pre_init 

115 def __make_operators(cls, name, bases, classdict): 

116 if not isinstance(cls.__operations__, tuple): 116 ↛ 117line 116 didn't jump to line 117, because the condition on line 116 was never true

117 raise TypeError("'__operations__' must be of type tuple") 

118 # add any new operations 

119 if len(bases) and 'Query' in globals() and issubclass(bases[-1], Query) \ 119 ↛ 121line 119 didn't jump to line 121, because the condition on line 119 was never true

120 and bases[-1].__operations__ is not cls.__operations__: 

121 new_operations = list(cls.__operations__) 

122 new_operations[0:0] = bases[-1].__operations__ 

123 cls.__operations__ = tuple(new_operations) 

124 for op in cls.__operations__: 

125 setattr(cls, op, cls.__build_operation(op)) 

126 

127 def __evaluate_key(self, key): 

128 if isinstance(key, tuple) and len(key) == 0: 128 ↛ 129line 128 didn't jump to line 129, because the condition on line 128 was never true

129 return key 

130 if isinstance(key, (tuple, list, np.ndarray)): 

131 return list(map(self.__evaluate_key, key)) 

132 else: 

133 if isinstance(key, Query): 

134 return key.evaluate() 

135 return key 

136 

137 def __getitem__(self, key): 

138 idx = self.__evaluate_key(key) 

139 return self.dataset[idx] 

140 

141 @docval({'name': 'dataset', 'type': ('array_data', Array), 'doc': 'the HDF5 file lazily evaluate'}) 

142 def __init__(self, **kwargs): 

143 super().__init__() 

144 self.__dataset = getargs('dataset', kwargs) 

145 

146 @property 

147 def dataset(self): 

148 return self.__dataset 

149 

150 @property 

151 def dtype(self): 

152 return self.__dataset.dtype 

153 

154 def __len__(self): 

155 return len(self.__dataset) 

156 

157 def __iter__(self): 

158 return iter(self.dataset) 

159 

160 def __next__(self): 

161 return next(self.dataset) 

162 

163 def next(self): 

164 return self.dataset.next() 

165 

166 

167class ReferenceResolver(metaclass=ABCMeta): 

168 """ 

169 A base class for classes that resolve references 

170 """ 

171 

172 @classmethod 

173 @abstractmethod 

174 def get_inverse_class(cls): 

175 """ 

176 Return the class the represents the ReferenceResolver 

177 that resolves references to the opposite type. 

178 

179 BuilderResolver.get_inverse_class should return a class 

180 that subclasses ContainerResolver. 

181 

182 ContainerResolver.get_inverse_class should return a class 

183 that subclasses BuilderResolver. 

184 """ 

185 pass 

186 

187 @abstractmethod 

188 def invert(self): 

189 """ 

190 Return an object that defers reference resolution 

191 but in the opposite direction. 

192 """ 

193 pass 

194 

195 

196class BuilderResolver(ReferenceResolver): 

197 """ 

198 A reference resolver that resolves references to Builders 

199 

200 Subclasses should implement the invert method and the get_inverse_class 

201 classmethod 

202 

203 BuilderResolver.get_inverse_class should return a class that subclasses 

204 ContainerResolver. 

205 """ 

206 

207 pass 

208 

209 

210class ContainerResolver(ReferenceResolver): 

211 """ 

212 A reference resolver that resolves references to Containers 

213 

214 Subclasses should implement the invert method and the get_inverse_class 

215 classmethod 

216 

217 ContainerResolver.get_inverse_class should return a class that subclasses 

218 BuilderResolver. 

219 """ 

220 

221 pass