Coverage for stricto/generic.py: 100%

193 statements  

« prev     ^ index     » next       coverage.py v7.4.1, created at 2024-02-29 10:26 +0100

1""" 

2Module providing the Generic() Class 

3This class must not be used directly 

4""" 

5import copy 

6from .error import Error, ErrorType 

7 

8 

9class GenericType: # pylint: disable=too-many-instance-attributes 

10 """ 

11 A generic type (class for int, string, etc) 

12 """ 

13 

14 def __init__(self, **kwargs): 

15 """ 

16 available arguments 

17 

18 description : A tring to describe the object 

19 default : The default value 

20 notNone : (boolean) must be required or not 

21 

22 """ 

23 self.root = None 

24 self.parent = None 

25 self.attribute_name = "" 

26 self._value = None 

27 self._old_value = None 

28 self._descrition = kwargs.pop("description", None) 

29 self._not_none = kwargs.pop("notNone", kwargs.pop("required", False)) 

30 self._union = kwargs.pop("union", kwargs.pop("in", None)) 

31 self.currently_doing_autoset = False 

32 constraint = kwargs.pop("constraint", kwargs.pop("constraints", [])) 

33 self._constraints = constraint if isinstance(constraint, list) else [constraint] 

34 

35 

36 self._default = kwargs.pop("default", None) 

37 self.check(self._default) 

38 self._value = self._default 

39 self._old_value = self._value 

40 

41 # transformation of the value before setting 

42 self._transform = kwargs.pop("transform", None) 

43 # the value is computed 

44 self._auto_set = kwargs.pop("set", kwargs.pop("compute", None)) 

45 # on change trigger 

46 self._on_change = kwargs.pop("onChange", kwargs.pop("onchange", None)) 

47 # this object exist 

48 self._exists = kwargs.pop("exists", kwargs.pop("existsIf", True)) 

49 

50 

51 def set_hierachy_attributs(self, root, parent, name): 

52 """ 

53 set the attribute root, parent and name for the current objecte related to the all structure 

54 """ 

55 self.root = root 

56 self.parent = parent 

57 self.attribute_name = name 

58 

59 def am_i_root(self): 

60 """ 

61 Check if this object is the root object 

62 """ 

63 if self.root == self: 

64 return True 

65 return False 

66 

67 def exists(self): 

68 """ 

69 Return True if the object Exist, othewise False. 

70 exist can be a function to make this field dependant from the value of another 

71 """ 

72 return self.get_args_or_execute_them(self._exists, self._value) 

73 

74 def path_name(self): 

75 """ 

76 return a string with the name of the object 

77 """ 

78 p = [] 

79 parent = self 

80 while parent is not None: 

81 p.insert(0, parent.attribute_name) 

82 parent = parent.parent 

83 return ".".join(p) 

84 

85 def get_other_value(self, other): 

86 """ 

87 return the value of the other object if GenericType 

88 """ 

89 if isinstance(other, GenericType): 

90 return other.get_value() 

91 return other 

92 

93 def __add__(self, other): 

94 """ 

95 add two objects 

96 """ 

97 b = self._value + self.get_other_value(other) 

98 r = self.__copy__() 

99 

100 r.set(b) 

101 return r 

102 

103 def __sub__(self, other): 

104 """ 

105 sub two objects 

106 """ 

107 b = self._value - self.get_other_value(other) 

108 r = self.__copy__() 

109 r.set(b) 

110 return r 

111 

112 def __mul__(self, other): 

113 """ 

114 mul two objects 

115 """ 

116 b = self._value * self.get_other_value(other) 

117 r = self.__copy__() 

118 r.set(b) 

119 return r 

120 

121 def __truediv__(self, other): 

122 """ 

123 div two objects 

124 """ 

125 b = self._value / self.get_other_value(other) 

126 r = self.__copy__() 

127 r.set(b) 

128 return r 

129 

130 def __floordiv__(self, other): 

131 """ 

132 floordiv two objects 

133 """ 

134 b = self._value // self.get_other_value(other) 

135 r = self.__copy__() 

136 r.set(b) 

137 return r 

138 

139 def __pow__(self, other): 

140 """ 

141 pow two objects 

142 """ 

143 b = self._value ** self.get_other_value(other) 

144 r = self.__copy__() 

145 r.set(b) 

146 return r 

147 

148 def __mod__(self, other): 

149 """ 

150 mod two objects 

151 """ 

152 b = self._value % self.get_other_value(other) 

153 r = self.__copy__() 

154 r.set(b) 

155 return r 

156 

157 def __rshift__(self, other): 

158 """ 

159 __rshift__ two objects 

160 """ 

161 b = self._value >> self.get_other_value(other) 

162 r = self.__copy__() 

163 r.set(b) 

164 return r 

165 

166 def __lshift__(self, other): 

167 """ 

168 __lshift__ two objects 

169 """ 

170 b = self._value << self.get_other_value(other) 

171 r = self.__copy__() 

172 r.set(b) 

173 return r 

174 

175 def __and__(self, other): 

176 """ 

177 __and__ two objects 

178 """ 

179 b = self._value & self.get_other_value(other) 

180 r = self.__copy__() 

181 r.set(b) 

182 return r 

183 

184 def __or__(self, other): 

185 """ 

186 __or__ two objects 

187 """ 

188 b = self._value | self.get_other_value(other) 

189 r = self.__copy__() 

190 r.set(b) 

191 return r 

192 

193 def __xor__(self, other): 

194 """ 

195 __xor__ two objects 

196 """ 

197 b = self._value ^ self.get_other_value(other) 

198 r = self.__copy__() 

199 r.set(b) 

200 return r 

201 

202 def __eq__(self, other): 

203 """ 

204 equality test two objects 

205 """ 

206 return self._value == self.get_other_value(other) 

207 

208 def __ne__(self, other): 

209 """ 

210 ne test two objects 

211 """ 

212 return self._value != self.get_other_value(other) 

213 

214 def __lt__(self, other): 

215 

216 """ 

217 lt test two objects 

218 """ 

219 return self._value < self.get_other_value(other) 

220 

221 def __le__(self, other): 

222 

223 """ 

224 le test two objects 

225 """ 

226 return self._value <= self.get_other_value(other) 

227 

228 def __gt__(self, other): 

229 

230 """ 

231 gt test two objects 

232 """ 

233 return self._value > self.get_other_value(other) 

234 

235 def __ge__(self, other): 

236 

237 """ 

238 ge test two objects 

239 """ 

240 return self._value >= self.get_other_value(other) 

241 

242 def __copy__(self): 

243 cls = self.__class__ 

244 result = cls.__new__(cls) 

245 result.__dict__.update(self.__dict__) 

246 return result 

247 

248 def copy(self): 

249 """ 

250 call copy 

251 """ 

252 return copy.copy(self) 

253 

254 def set(self, value): 

255 """ 

256 Fill with a value or raise an Error if not valid 

257 """ 

258 

259 if self.exists() is False: 

260 raise Error(ErrorType.NOTALIST, "locked", self.path_name()) 

261 

262 if self._auto_set is not None: 

263 if self.root.currently_doing_autoset is False: 

264 raise Error(ErrorType.READONLY, "Cannot modify value", self.path_name()) 

265 

266 corrected_value = ( 

267 value.get_value() if type(value) == type(self) else value # pylint: disable=unidiomatic-typecheck 

268 ) 

269 if callable(self._transform): 

270 corrected_value = self._transform(corrected_value, self.root) 

271 

272 self.check(corrected_value) 

273 return self.set_value_without_checks(corrected_value) 

274 

275 def set_value_without_checks(self, value): 

276 """ 

277 return True if some changement, otherwise False 

278 """ 

279 

280 if self.exists() is False: 

281 raise Error(ErrorType.NOTALIST, "locked", self.path_name()) 

282 

283 if self._auto_set is not None: 

284 if self.root.currently_doing_autoset is False: 

285 raise Error(ErrorType.READONLY, "Cannot modify value", self.path_name()) 

286 

287 # transform the value before the check 

288 corrected_value = ( 

289 value.get_value() if type(value) == type(self) else value # pylint: disable=unidiomatic-typecheck 

290 ) 

291 

292 self._old_value = self._value 

293 self._value = self._default if corrected_value is None else corrected_value 

294 

295 if self._old_value == self._value: 

296 return False 

297 

298 if callable(self._on_change): 

299 self._on_change(self._old_value, value, self.root) 

300 if self.root is not None: 

301 self.root.auto_set() 

302 return True 

303 

304 def auto_set(self): 

305 """ 

306 compute automatically a value because another value as changed somewhere. 

307 (related to set=flag) 

308 """ 

309 if callable(self._auto_set) is False: 

310 return 

311 new_value = self._auto_set(self.root) 

312 self.set_value_without_checks(new_value) 

313 

314 def get_value(self): 

315 """ 

316 get the value 

317 """ 

318 return self._value 

319 

320 def __repr__(self): 

321 return self._value.__repr__() 

322 

323 def check(self, value): 

324 """ 

325 check if complain to model or return an Error 

326 """ 

327 

328 # transform the value before the check 

329 corrected_value = value 

330 if callable(self._transform): 

331 corrected_value = self._transform(value, self.root) 

332 

333 # handle the None value 

334 if corrected_value is None: 

335 if self._not_none is True: 

336 raise Error(ErrorType.NULL, "Cannot be empty", self.path_name()) 

337 return True 

338 

339 # Check correct type or raise an Error 

340 self.check_type(corrected_value) 

341 

342 # check constraints or raise an Error 

343 self.check_constraints(corrected_value) 

344 

345 return True 

346 

347 def __getattr__(self, k): 

348 """ 

349 replicate all atributes from value, but prefere self attribute first. 

350 """ 

351 #if k in self.__dict__: 

352 # return self.__dict__[k] 

353 if hasattr(self._value, k): 

354 return getattr(self._value, k) 

355 return None 

356 

357 def check_type(self, value): # pylint: disable=unused-argument 

358 """ 

359 Check if the type is correct. 

360 must be overwritten 

361 """ 

362 # return True 

363 

364 def check_constraints(self, value): 

365 """ 

366 Check all constraints 

367 """ 

368 # Union constraint 

369 if self._union: 

370 l = self.get_args_or_execute_them(self._union, value) 

371 if not isinstance(l, list): 

372 raise Error( 

373 ErrorType.UNION, "Union constraint not list", self.path_name() 

374 ) 

375 if value not in l: 

376 raise Error(ErrorType.UNION, "not in list", self.path_name()) 

377 

378 # ---- constraints as functions 

379 for constraint in self._constraints: 

380 if callable(constraint) is not True: 

381 raise Error( 

382 ErrorType.NOTCALLABLE, "constraint not callable", self.path_name() 

383 ) 

384 r = constraint(value, self.root) 

385 if r is False: 

386 raise Error( 

387 ErrorType.CONSTRAINT, "constraint not validated", self.path_name() 

388 ) 

389 return True 

390 

391 def get_args_or_execute_them(self, arg, value): 

392 """ 

393 get element from an argument, or if it is callable 

394 execute the arg as a function to retreive the information 

395 example : 

396 min = 12 -> return 12 

397 min = computeMin -> return computeMin( value ) 

398 """ 

399 if callable(arg): 

400 return arg(value, self.root) 

401 return arg