Validators on one field
Lesson 17's Ticket accepted a priority of ninety-nine and an issue nobody at the shop has ever heard of. The on= argument from lesson 15 takes a JSON path, and that is where field rules go.
from guardrails_ai.valid_choices import ValidChoices
from guardrails_ai.valid_range import ValidRange
tickets = (Guard.for_pydantic(Ticket)
.use(ValidChoices(choices=["late", "damaged", "wrong item"], on_fail="noop"), on="$.issue")
.use(ValidRange(min=1, max=3, on_fail="noop"), on="$.priority"))
outcome = tickets.parse('{"order_id": "8821", "issue": "lost", "priority": 99}', num_reasks=0)
for summary in sorted(outcome.validation_summaries, key=lambda s: s.property_path):
print(summary.property_path, summary.validator_name, "|", summary.failure_reason)$.issue and $.priority are JSON paths into the parsed object, and property_path in the summary tells you which field complained. Two use calls, two different targets, and both stick, which is the rule lesson 6 ended on.
Pydantic has its own way of attaching validators, inside Field(validators=[...]). It still works and the Guardrails documentation uses it throughout, but on current Pydantic it prints a deprecation warning about extra keyword arguments on Field. The on= form does the same job and keeps the model class clean.
filter, the half that was deferred
Lesson 11 left filter unfinished, because on a plain string it does the same thing as refrain. On an object it does what its name says.
tickets = Guard.for_pydantic(Ticket).use(
ValidChoices(choices=["late", "damaged"], on_fail="filter"), on="$.issue")
outcome = tickets.parse('{"order_id": "8821", "issue": "lost", "priority": 2}', num_reasks=0)
print(outcome.validated_output)
print(tickets.history.last.iterations.last.guarded_output)Two different answers to the same question. The iteration holds the ticket with the bad field removed, exactly as the documentation promises. The outcome holds None, because of the rule from lesson 11: guarded_output on the call only returns a value when the Guard passed or every failure was a noop, and a filtered field is neither.
So the sentence in the on-fail table, that filter returns the rest of the generated output, is true one level below the object you actually read. If you want the filtered ticket, take it from the iteration and decide for yourself whether a ticket with a missing field is worth filing.
- Add
ValidLengthon$.order_idso a four digit number is the only thing that fits. - Set the issue rule to
fixand giveValidChoicesnothing to fix with. Lesson 10 predicted what you get. - Use
on="$.priority"twice on the same Guard and confirm the overwrite rule from lesson 6 applies per path.
Slow is fine. Stopping is the only problem.