Validated
Validated[T, E] is Valid(value) or Invalid(errors). Use it when a form, config file or batch should report every error in one pass.
from pyrula import Validated, Valid, Invalidzip combines two results. Two Valid give Valid((a, b)); any Invalid gives an Invalid with all errors concatenated.
def check_name(name: str) -> Validated[str, str]: return Validated.valid(name) if name.strip() else Validated.invalid("name cannot be blank")
def check_age(age: int) -> Validated[int, str]: return Validated.valid(age) if 0 <= age <= 130 else Validated.invalid("age must be between 0 and 130")
result = check_name("").zip(check_age(200))# Invalid(['name cannot be blank', 'age must be between 0 and 130'])Traversal and sequencing
Section titled “Traversal and sequencing”traverse(values, fn) runs fn on every input, including those after an Invalid. sequence(values) collects existing results. Errors are flattened in encounter order, and success holds a list.
Validated.traverse(["Ada", "Lin"], check_name) # Valid(['Ada', 'Lin'])Validated.traverse(["", "Ada", ""], check_name)# Invalid(['name cannot be blank', 'name cannot be blank'])
Validated.sequence([Invalid(["name", "email"]), Valid(2), Invalid("age")])# Invalid(['name', 'email', 'age'])Callbacks and elements must be Validated, or TypeError is raised. Iterator and callback exceptions propagate and are not collected as errors. For stop-at-first-error, use Either.traverse.
Constructors
Section titled “Constructors”| Call | Result |
|---|---|
Validated.valid(v) / Valid(v) | Valid(v) |
Validated.invalid(e) | Invalid([e]) |
Validated.invalid([e1, e2]) | Invalid([e1, e2]) |
Validated.cond(test, value, error) | Valid(value) if test else Invalid([error]) |
Transform and fold
Section titled “Transform and fold”Validated.valid(2).map(lambda x: x * 10) # Valid(20)Invalid("too short").map_err(lambda errs: [e.upper() for e in errs])# Invalid(['TOO SHORT'])
result.fold( lambda errs: f"rejected: {', '.join(errs)}", lambda val: f"accepted: {val}",)get_or_else takes an eager default.
Matching
Section titled “Matching”match Validated.sequence([check_name(""), check_name("Ada")]): case Valid(names): message = f"accepted: {names}" case Invalid(errors): message = f"rejected: {', '.join(errors)}"
assert message == "rejected: name cannot be blank"Errors are a tuple
Section titled “Errors are a tuple”Invalid.errors is an immutable tuple, so an Invalid is hashable when its errors are. The constructor takes a list or tuple as the whole error collection and wraps anything else as one error. repr and the JSON form write the errors as a list.
Invalid("a").errors # ('a',)Invalid(("a", "b")).errors # ('a', 'b'): two errors, not one tuple errorInvalid((("code", "msg"),)) # one error that is itself a tupleEither
Section titled “Either”from pyrula import Ok, Err
Validated.from_either(Err("e")) # Invalid(['e'])Invalid("e").to_either() # Err(('e',)); the full error tuple is the payload@do and @do_async do not accept Validated. Use zip, sequence or traverse to accumulate, and Either with @do for dependent steps. The JSON form is in JSON form.