Coverage for src/dataknobs_data/validation_old_backup/constraints.py: 0%

179 statements  

« prev     ^ index     » next       coverage.py v7.10.3, created at 2025-08-15 12:32 -0500

1"""Field constraints for schema validation.""" 

2 

3import re 

4from abc import ABC, abstractmethod 

5from typing import Any, Callable, Dict, List, Optional, Set, Union 

6 

7 

8class Constraint(ABC): 

9 """Base class for field constraints.""" 

10 

11 def __init__(self, name: str = ""): 

12 """Initialize constraint. 

13  

14 Args: 

15 name: Constraint name for error messages 

16 """ 

17 self.name = name or self.__class__.__name__ 

18 

19 @abstractmethod 

20 def validate(self, value: Any) -> bool: 

21 """Validate a value against this constraint. 

22  

23 Args: 

24 value: Value to validate 

25  

26 Returns: 

27 True if valid, False otherwise 

28 """ 

29 pass 

30 

31 @abstractmethod 

32 def get_error_message(self, value: Any) -> str: 

33 """Get error message for failed validation. 

34  

35 Args: 

36 value: The invalid value 

37  

38 Returns: 

39 Error message string 

40 """ 

41 pass 

42 

43 def to_dict(self) -> Dict[str, Any]: 

44 """Convert constraint to dictionary.""" 

45 return { 

46 'type': self.__class__.__name__, 

47 'name': self.name 

48 } 

49 

50 @classmethod 

51 def from_dict(cls, data: Dict[str, Any]) -> "Constraint": 

52 """Create constraint from dictionary.""" 

53 constraint_type = data.get('type', 'Constraint') 

54 

55 # Map type names to classes 

56 type_map = { 

57 'RequiredConstraint': RequiredConstraint, 

58 'UniqueConstraint': UniqueConstraint, 

59 'MinValueConstraint': MinValueConstraint, 

60 'MaxValueConstraint': MaxValueConstraint, 

61 'MinLengthConstraint': MinLengthConstraint, 

62 'MaxLengthConstraint': MaxLengthConstraint, 

63 'PatternConstraint': PatternConstraint, 

64 'EnumConstraint': EnumConstraint, 

65 'CustomConstraint': CustomConstraint, 

66 } 

67 

68 constraint_class = type_map.get(constraint_type, cls) 

69 return constraint_class(**data) 

70 

71 

72class RequiredConstraint(Constraint): 

73 """Constraint that requires a non-null value.""" 

74 

75 def __init__(self, allow_empty: bool = False, **kwargs): 

76 """Initialize required constraint. 

77  

78 Args: 

79 allow_empty: Whether to allow empty strings/collections 

80 """ 

81 super().__init__(**kwargs) 

82 self.allow_empty = allow_empty 

83 

84 def validate(self, value: Any) -> bool: 

85 """Check if value is not None.""" 

86 if value is None: 

87 return False 

88 

89 if not self.allow_empty: 

90 if isinstance(value, (str, list, dict)) and len(value) == 0: 

91 return False 

92 

93 return True 

94 

95 def get_error_message(self, value: Any) -> str: 

96 """Get error message.""" 

97 if value is None: 

98 return "Value is required" 

99 return "Value cannot be empty" 

100 

101 def to_dict(self) -> Dict[str, Any]: 

102 """Convert to dictionary.""" 

103 data = super().to_dict() 

104 data['allow_empty'] = self.allow_empty 

105 return data 

106 

107 

108class UniqueConstraint(Constraint): 

109 """Constraint that requires unique values.""" 

110 

111 def __init__(self, scope: str = "global", **kwargs): 

112 """Initialize unique constraint. 

113  

114 Args: 

115 scope: Uniqueness scope ('global', 'collection', etc.) 

116 """ 

117 super().__init__(**kwargs) 

118 self.scope = scope 

119 self.seen_values: Set[Any] = set() 

120 

121 def validate(self, value: Any) -> bool: 

122 """Check if value is unique.""" 

123 if value in self.seen_values: 

124 return False 

125 self.seen_values.add(value) 

126 return True 

127 

128 def get_error_message(self, value: Any) -> str: 

129 """Get error message.""" 

130 return f"Value must be unique (duplicate: {value})" 

131 

132 def reset(self) -> None: 

133 """Reset seen values.""" 

134 self.seen_values.clear() 

135 

136 def to_dict(self) -> Dict[str, Any]: 

137 """Convert to dictionary.""" 

138 data = super().to_dict() 

139 data['scope'] = self.scope 

140 return data 

141 

142 

143class MinValueConstraint(Constraint): 

144 """Constraint for minimum numeric value.""" 

145 

146 def __init__(self, min_value: Union[int, float], inclusive: bool = True, **kwargs): 

147 """Initialize minimum value constraint. 

148  

149 Args: 

150 min_value: Minimum allowed value 

151 inclusive: Whether the minimum is inclusive 

152 """ 

153 super().__init__(**kwargs) 

154 self.min_value = min_value 

155 self.inclusive = inclusive 

156 

157 def validate(self, value: Any) -> bool: 

158 """Check if value meets minimum.""" 

159 try: 

160 numeric_value = float(value) 

161 if self.inclusive: 

162 return numeric_value >= self.min_value 

163 return numeric_value > self.min_value 

164 except (TypeError, ValueError): 

165 return False 

166 

167 def get_error_message(self, value: Any) -> str: 

168 """Get error message.""" 

169 op = ">=" if self.inclusive else ">" 

170 return f"Value must be {op} {self.min_value} (got {value})" 

171 

172 def to_dict(self) -> Dict[str, Any]: 

173 """Convert to dictionary.""" 

174 data = super().to_dict() 

175 data['min_value'] = self.min_value 

176 data['inclusive'] = self.inclusive 

177 return data 

178 

179 

180class MaxValueConstraint(Constraint): 

181 """Constraint for maximum numeric value.""" 

182 

183 def __init__(self, max_value: Union[int, float], inclusive: bool = True, **kwargs): 

184 """Initialize maximum value constraint. 

185  

186 Args: 

187 max_value: Maximum allowed value 

188 inclusive: Whether the maximum is inclusive 

189 """ 

190 super().__init__(**kwargs) 

191 self.max_value = max_value 

192 self.inclusive = inclusive 

193 

194 def validate(self, value: Any) -> bool: 

195 """Check if value meets maximum.""" 

196 try: 

197 numeric_value = float(value) 

198 if self.inclusive: 

199 return numeric_value <= self.max_value 

200 return numeric_value < self.max_value 

201 except (TypeError, ValueError): 

202 return False 

203 

204 def get_error_message(self, value: Any) -> str: 

205 """Get error message.""" 

206 op = "<=" if self.inclusive else "<" 

207 return f"Value must be {op} {self.max_value} (got {value})" 

208 

209 def to_dict(self) -> Dict[str, Any]: 

210 """Convert to dictionary.""" 

211 data = super().to_dict() 

212 data['max_value'] = self.max_value 

213 data['inclusive'] = self.inclusive 

214 return data 

215 

216 

217class MinLengthConstraint(Constraint): 

218 """Constraint for minimum string/collection length.""" 

219 

220 def __init__(self, min_length: int, **kwargs): 

221 """Initialize minimum length constraint. 

222  

223 Args: 

224 min_length: Minimum required length 

225 """ 

226 super().__init__(**kwargs) 

227 self.min_length = min_length 

228 

229 def validate(self, value: Any) -> bool: 

230 """Check if value meets minimum length.""" 

231 try: 

232 return len(value) >= self.min_length 

233 except TypeError: 

234 return False 

235 

236 def get_error_message(self, value: Any) -> str: 

237 """Get error message.""" 

238 try: 

239 actual_length = len(value) 

240 return f"Length must be at least {self.min_length} (got {actual_length})" 

241 except TypeError: 

242 return f"Value must have a length (got {type(value).__name__})" 

243 

244 def to_dict(self) -> Dict[str, Any]: 

245 """Convert to dictionary.""" 

246 data = super().to_dict() 

247 data['min_length'] = self.min_length 

248 return data 

249 

250 

251class MaxLengthConstraint(Constraint): 

252 """Constraint for maximum string/collection length.""" 

253 

254 def __init__(self, max_length: int, **kwargs): 

255 """Initialize maximum length constraint. 

256  

257 Args: 

258 max_length: Maximum allowed length 

259 """ 

260 super().__init__(**kwargs) 

261 self.max_length = max_length 

262 

263 def validate(self, value: Any) -> bool: 

264 """Check if value meets maximum length.""" 

265 try: 

266 return len(value) <= self.max_length 

267 except TypeError: 

268 return False 

269 

270 def get_error_message(self, value: Any) -> str: 

271 """Get error message.""" 

272 try: 

273 actual_length = len(value) 

274 return f"Length must be at most {self.max_length} (got {actual_length})" 

275 except TypeError: 

276 return f"Value must have a length (got {type(value).__name__})" 

277 

278 def to_dict(self) -> Dict[str, Any]: 

279 """Convert to dictionary.""" 

280 data = super().to_dict() 

281 data['max_length'] = self.max_length 

282 return data 

283 

284 

285class PatternConstraint(Constraint): 

286 """Constraint for regex pattern matching.""" 

287 

288 def __init__(self, pattern: str, flags: int = 0, **kwargs): 

289 """Initialize pattern constraint. 

290  

291 Args: 

292 pattern: Regular expression pattern 

293 flags: Regex flags (e.g., re.IGNORECASE) 

294 """ 

295 super().__init__(**kwargs) 

296 self.pattern = pattern 

297 self.flags = flags 

298 self._regex = re.compile(pattern, flags) 

299 

300 def validate(self, value: Any) -> bool: 

301 """Check if value matches pattern.""" 

302 try: 

303 string_value = str(value) 

304 return self._regex.match(string_value) is not None 

305 except Exception: 

306 return False 

307 

308 def get_error_message(self, value: Any) -> str: 

309 """Get error message.""" 

310 return f"Value must match pattern: {self.pattern} (got {value})" 

311 

312 def to_dict(self) -> Dict[str, Any]: 

313 """Convert to dictionary.""" 

314 data = super().to_dict() 

315 data['pattern'] = self.pattern 

316 data['flags'] = self.flags 

317 return data 

318 

319 

320class EnumConstraint(Constraint): 

321 """Constraint for enumerated values.""" 

322 

323 def __init__(self, allowed_values: List[Any], **kwargs): 

324 """Initialize enum constraint. 

325  

326 Args: 

327 allowed_values: List of allowed values 

328 """ 

329 super().__init__(**kwargs) 

330 self.allowed_values = set(allowed_values) 

331 

332 def validate(self, value: Any) -> bool: 

333 """Check if value is in allowed set.""" 

334 return value in self.allowed_values 

335 

336 def get_error_message(self, value: Any) -> str: 

337 """Get error message.""" 

338 allowed_str = ", ".join(str(v) for v in sorted(self.allowed_values)) 

339 return f"Value must be one of: {allowed_str} (got {value})" 

340 

341 def to_dict(self) -> Dict[str, Any]: 

342 """Convert to dictionary.""" 

343 data = super().to_dict() 

344 data['allowed_values'] = list(self.allowed_values) 

345 return data 

346 

347 

348class CustomConstraint(Constraint): 

349 """Constraint with custom validation function.""" 

350 

351 def __init__( 

352 self, 

353 validator: Optional[Callable[[Any], bool]] = None, 

354 error_message: str = "Custom validation failed", 

355 **kwargs 

356 ): 

357 """Initialize custom constraint. 

358  

359 Args: 

360 validator: Custom validation function 

361 error_message: Error message for failures 

362 """ 

363 super().__init__(**kwargs) 

364 self.validator = validator or (lambda x: True) 

365 self.error_message = error_message 

366 

367 def validate(self, value: Any) -> bool: 

368 """Apply custom validation.""" 

369 try: 

370 return self.validator(value) 

371 except Exception: 

372 return False 

373 

374 def get_error_message(self, value: Any) -> str: 

375 """Get error message.""" 

376 return self.error_message.format(value=value) if "{value}" in self.error_message else self.error_message 

377 

378 def to_dict(self) -> Dict[str, Any]: 

379 """Convert to dictionary.""" 

380 data = super().to_dict() 

381 data['error_message'] = self.error_message 

382 # Note: Cannot serialize arbitrary functions 

383 return data