Coverage for src/dataknobs_data/validation_v2/result.py: 100%
43 statements
« prev ^ index » next coverage.py v7.10.3, created at 2025-08-15 12:29 -0500
« prev ^ index » next coverage.py v7.10.3, created at 2025-08-15 12:29 -0500
1"""
2Validation result types with consistent, predictable behavior.
3"""
5from dataclasses import dataclass, field
6from typing import Any, List, Optional, Dict, Set
9@dataclass
10class ValidationResult:
11 """
12 Unified result object for all validation operations.
14 This class provides a consistent return type for all validation operations,
15 making the API predictable and easy to use.
16 """
18 valid: bool
19 value: Any # The (possibly coerced) value
20 errors: List[str] = field(default_factory=list)
21 warnings: List[str] = field(default_factory=list)
23 def __bool__(self) -> bool:
24 """Allow 'if result:' usage to check validity."""
25 return self.valid
27 def merge(self, other: 'ValidationResult') -> 'ValidationResult':
28 """
29 Combine results for composite validation.
31 Args:
32 other: Another ValidationResult to merge with this one
34 Returns:
35 New ValidationResult with combined state
36 """
37 return ValidationResult(
38 valid=self.valid and other.valid,
39 value=other.value if other.valid else self.value,
40 errors=self.errors + other.errors,
41 warnings=self.warnings + other.warnings
42 )
44 def add_error(self, error: str) -> 'ValidationResult':
45 """
46 Add an error and mark as invalid (fluent API).
48 Args:
49 error: Error message to add
51 Returns:
52 Self for chaining
53 """
54 self.errors.append(error)
55 self.valid = False
56 return self
58 def add_warning(self, warning: str) -> 'ValidationResult':
59 """
60 Add a warning without affecting validity (fluent API).
62 Args:
63 warning: Warning message to add
65 Returns:
66 Self for chaining
67 """
68 self.warnings.append(warning)
69 return self
71 @classmethod
72 def success(cls, value: Any, warnings: Optional[List[str]] = None) -> 'ValidationResult':
73 """
74 Create a successful validation result.
76 Args:
77 value: The validated value
78 warnings: Optional list of warnings
80 Returns:
81 Successful ValidationResult
82 """
83 return cls(
84 valid=True,
85 value=value,
86 errors=[],
87 warnings=warnings or []
88 )
90 @classmethod
91 def failure(cls, value: Any, errors: List[str], warnings: Optional[List[str]] = None) -> 'ValidationResult':
92 """
93 Create a failed validation result.
95 Args:
96 value: The value that failed validation
97 errors: List of error messages
98 warnings: Optional list of warnings
100 Returns:
101 Failed ValidationResult
102 """
103 return cls(
104 valid=False,
105 value=value,
106 errors=errors,
107 warnings=warnings or []
108 )
111@dataclass
112class ValidationContext:
113 """
114 Context for stateful validation operations.
116 Used by constraints like Unique that need to track state across
117 multiple validations.
118 """
120 seen_values: Dict[str, Set[Any]] = field(default_factory=dict)
121 metadata: Dict[str, Any] = field(default_factory=dict)
123 def has_seen(self, field: str, value: Any) -> bool:
124 """
125 Check if a value has been seen for a field.
127 Args:
128 field: Field name
129 value: Value to check
131 Returns:
132 True if value has been seen for this field
133 """
134 return field in self.seen_values and value in self.seen_values[field]
136 def mark_seen(self, field: str, value: Any) -> None:
137 """
138 Mark a value as seen for a field.
140 Args:
141 field: Field name
142 value: Value to mark as seen
143 """
144 if field not in self.seen_values:
145 self.seen_values[field] = set()
146 self.seen_values[field].add(value)
148 def clear(self, field: Optional[str] = None) -> None:
149 """
150 Clear seen values.
152 Args:
153 field: Optional field to clear. If None, clears all fields.
154 """
155 if field:
156 self.seen_values.pop(field, None)
157 else:
158 self.seen_values.clear()
160 def set_metadata(self, key: str, value: Any) -> None:
161 """
162 Store metadata in the context.
164 Args:
165 key: Metadata key
166 value: Metadata value
167 """
168 self.metadata[key] = value
170 def get_metadata(self, key: str, default: Any = None) -> Any:
171 """
172 Retrieve metadata from the context.
174 Args:
175 key: Metadata key
176 default: Default value if key not found
178 Returns:
179 Metadata value or default
180 """
181 return self.metadata.get(key, default)