Coverage for src/dataknobs_data/validation/result.py: 59%
44 statements
« prev ^ index » next coverage.py v7.11.0, created at 2025-10-29 14:14 -0600
« prev ^ index » next coverage.py v7.11.0, created at 2025-10-29 14:14 -0600
1"""Validation result types with consistent, predictable behavior.
2"""
4from __future__ import annotations
6from dataclasses import dataclass, field
7from typing import Any
10@dataclass
11class ValidationResult:
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 """Combine results for composite validation.
30 Args:
31 other: Another ValidationResult to merge with this one
33 Returns:
34 New ValidationResult with combined state
35 """
36 return ValidationResult(
37 valid=self.valid and other.valid,
38 value=other.value if other.valid else self.value,
39 errors=self.errors + other.errors,
40 warnings=self.warnings + other.warnings
41 )
43 def add_error(self, error: str) -> ValidationResult:
44 """Add an error and mark as invalid (fluent API).
46 Args:
47 error: Error message to add
49 Returns:
50 Self for chaining
51 """
52 self.errors.append(error)
53 self.valid = False
54 return self
56 def add_warning(self, warning: str) -> ValidationResult:
57 """Add a warning without affecting validity (fluent API).
59 Args:
60 warning: Warning message to add
62 Returns:
63 Self for chaining
64 """
65 self.warnings.append(warning)
66 return self
68 @classmethod
69 def success(cls, value: Any, warnings: list[str] | None = None) -> ValidationResult:
70 """Create a successful validation result.
72 Args:
73 value: The validated value
74 warnings: Optional list of warnings
76 Returns:
77 Successful ValidationResult
78 """
79 return cls(
80 valid=True,
81 value=value,
82 errors=[],
83 warnings=warnings or []
84 )
86 @classmethod
87 def failure(cls, value: Any, errors: list[str], warnings: list[str] | None = None) -> ValidationResult:
88 """Create a failed validation result.
90 Args:
91 value: The value that failed validation
92 errors: List of error messages
93 warnings: Optional list of warnings
95 Returns:
96 Failed ValidationResult
97 """
98 return cls(
99 valid=False,
100 value=value,
101 errors=errors,
102 warnings=warnings or []
103 )
106@dataclass
107class ValidationContext:
108 """Context for stateful validation operations.
110 Used by constraints like Unique that need to track state across
111 multiple validations.
112 """
114 seen_values: dict[str, set[Any]] = field(default_factory=dict)
115 metadata: dict[str, Any] = field(default_factory=dict)
117 def has_seen(self, field: str, value: Any) -> bool:
118 """Check if a value has been seen for a field.
120 Args:
121 field: Field name
122 value: Value to check
124 Returns:
125 True if value has been seen for this field
126 """
127 return field in self.seen_values and value in self.seen_values[field]
129 def mark_seen(self, field: str, value: Any) -> None:
130 """Mark a value as seen for a field.
132 Args:
133 field: Field name
134 value: Value to mark as seen
135 """
136 if field not in self.seen_values:
137 self.seen_values[field] = set()
138 self.seen_values[field].add(value)
140 def clear(self, field: str | None = None) -> None:
141 """Clear seen values.
143 Args:
144 field: Optional field to clear. If None, clears all fields.
145 """
146 if field:
147 self.seen_values.pop(field, None)
148 else:
149 self.seen_values.clear()
151 def set_metadata(self, key: str, value: Any) -> None:
152 """Store metadata in the context.
154 Args:
155 key: Metadata key
156 value: Metadata value
157 """
158 self.metadata[key] = value
160 def get_metadata(self, key: str, default: Any = None) -> Any:
161 """Retrieve metadata from the context.
163 Args:
164 key: Metadata key
165 default: Default value if key not found
167 Returns:
168 Metadata value or default
169 """
170 return self.metadata.get(key, default)