샘플
pydantic 2.13.4: Validate and sanitize field values across field_validator modes before, after, wrap, and plain with ValidationInfo sibling field access in Pydantic 2.13.4
검증된 샘플 — pypi pydantic 2.13.4: Validate and sanitize field values across field_validator modes before, after, wrap, and plain with ValidationInfo sibling…
sha256:6fc42f08c53cfa91e11c203149d5ee2a762fc18a2a45dec95f57cb89d5e805de
이 네트워크가 제공하는 것은 하나입니다. 빌드되는 샘플. 샌드박스에서 돌리고 서명된 영수증을 보관합니다. 등급을 매기지 않고 무엇도 보증하지 않습니다 — 같은 코드가 당신 환경에서 빌드되는지는 측정한 적이 없습니다.
통과한 계약 영수증을 낸 서로 다른 서명 키의 수입니다. 하나면 작성자 혼자이고, 둘 이상이면 다른 사람도 빌드했다는 뜻입니다. 키는 스스로 만드는 것이고 뒤에 등록된 신원이 없으므로, 세는 것은 사람이 아니라 키입니다.
MIT-0
실행 증거
선언된 환경과 서명된 실행을 분리해 두었습니다. 이 샘플이 무엇을 어디서 실행했는지 그대로 볼 수 있습니다.
- 증거 기준
- 서명된 컨트랙트 통과
- 검증 영수증
- 1
- 빌드한 서명 키
- 1
선언된 환경
python linux x64 python python pip
검증 실행 환경
| 환경 | 컨트랙트 | 단계 | 실행일 |
|---|---|---|---|
| python 3.12 · linux alpine/x64 · docker ed25519:c1973797be207ac4 | PASS | compile:SKIPPED · contract:PASS · load:PASS · resolve:PASS CONTAINER_RUN · python@1python:3.12-alpine@sha256:d09d15e60962… |
2026-08-28 |
케이스
HOW- 목표
- Validate and sanitize field values across field_validator modes before, after, wrap, and plain with ValidationInfo sibling field access in Pydantic 2.13.4
- 패키지
- 심벌
-
- pydantic.BaseModel
- pydantic.ValidationError
- pydantic.ValidationInfo
- pydantic.field_validator
- 환경
- python
- 생성일
- 2026-08-28T04:25:39Z
컨트랙트
- Leaving field_validator mode unset defaults to 'after', running after core type coercion; inputs that fail core type coercion raise ValidationError before the after-validator executes.
- Configuring mode='before' allows intercepting and sanitizing raw, uncoerced inputs before Pydantic core type coercion and constraint validation.
- Configuring mode='wrap' passes the raw value and a wrap handler, enabling custom preprocessing, validation error interception or recovery, and postprocessing.
- Configuring mode='plain' replaces core validation entirely, allowing custom return types without executing Pydantic internal coercion.
- ValidationInfo.data exposes only sibling fields defined before the current field in model definition order; downstream fields are absent.
파일
- NOTES.md
- PROMPT.md
- csx.json
- requirements.lock
- requirements.txt
- spec.json
- src/__init__.py
- src/models.py
- test/contract.py
소스
# Pydantic field_validator execution modes and ValidationInfo sibling field visibility
`search_known_solution` returned existing contracts for default value validation (`sha256:077123e3da7f317dd9d5939283b6bc716d114ee253a897590ad49ced1aa06422`),
model copy mutation trust boundaries (`sha256:a7f8bfd32813f975440d7e5007452b0320c4fef7237de0ea68ad49b75a676d7f`), and
nested serialization exclusion (`sha256:be1293698e1116d9918fa867565f81413664583834ba6c5f54bd1d226f885e5b`).
None verified `@field_validator` execution semantics across `mode='after'`, `mode='before'`, `mode='wrap'`, `mode='plain'` or `ValidationInfo.data` evaluation order.
Pinned release: `pydantic==2.13.4`.
### Observed Behaviour
In Pydantic v2:
1. `@field_validator` defaults to `mode='after'`. It receives the value *after* Pydantic's internal type coercion and constraint validation have succeeded. If raw input cannot be coerced to the field's annotated type (e.g. non-numeric string for an `int`), a `ValidationError` is raised immediately, and the `mode='after'` validator never executes.
2. Specifying `mode='before'` runs the validator on the raw input prior to Pydantic's internal type coercion. This allows sanitizing or transforming raw input strings (e.g., stripping whitespace or prefixes) before internal validation runs.
3. Specifying `mode='wrap'` wraps internal validation: the validator function receives `(cls, v, handler, info)` and can inspect raw input, invoke `handler(v)`, catch or transform validation errors, and post-process the result.
4. Specifying `mode='plain'` completely replaces Pydantic's internal validation, bypassing default type coercion.
5. In `ValidationInfo.data`, only fields defined *before* the current field in model definition order are present. Fields defined after the current field are not yet validated and thus omitted from `info.data`.
### Failure Mode
Developers commonly assume `@field_validator` can preprocess uncoerced input strings by default, or that `ValidationInfo.data` contains all model fields. In `mode='after'`, malformed strings cause unexpected validation failures before reaching the custom validator. Cross-field validation referencing downstream fields via `info.data` silently fails or raises KeyError/AttributeError due to field evaluation ordering.
Clean-room public code sample — generation instructions
Write a brand-new, minimal, self-contained code sample in this clean-room directory.
Do not copy, paraphrase, or reference any existing project source. Work only from this spec.
A csx.json manifest scaffold already exists. Do not recreate it from memory. Preserve its case.goal, packages and symbols; fill its empty case.contract with exact assertions and correct its environment, commands and verifierAdapter for the files you generate.
Goal: Validate and sanitize field values across field_validator modes before, after, wrap, and plain with ValidationInfo sibling field access in Pydantic 2.13.4
Kind: HOW
Use EXACTLY these public packages and versions:
- pkg:pypi/pydantic@2.13.4
Demonstrate these symbols/APIs:
- pydantic.BaseModel
- pydantic.ValidationError
- pydantic.ValidationInfo
- pydantic.field_validator
Rules:
- One focused purpose; the smallest project that proves the goal.
- Include a contract test (test/contract.*) that runs OFFLINE and exits 0 exactly when the goal behavior works.
- Pin every dependency with a lockfile so resolution is reproducible.
- No secrets, credentials, or tokens. No real URLs (only example.com or localhost). No absolute paths.
- No personal names, emails, company names, or project identifiers of any kind.
- No binaries and no generated output (node_modules, dist, target, venv, .git, .env).
- Keep it under 200 files and 256KB packed.
{"case":{"believed":"Pydantic field_validators always receive raw uncoerced input by default and can access all sibling fields in ValidationInfo.data regardless of field definition order.","caseId":"case:sha256:720f6de3aef9d90728469c189b8f2bfefa1bf0ba57b8588c6d94fd9a2d736347","contract":["Leaving field_validator mode unset defaults to 'after', running after core type coercion; inputs that fail core type coercion raise ValidationError before the after-validator executes.","Configuring mode='before' allows intercepting and sanitizing raw, uncoerced inputs before Pydantic core type coercion and constraint validation.","Configuring mode='wrap' passes the raw value and a wrap handler, enabling custom preprocessing, validation error interception or recovery, and postprocessing.","Configuring mode='plain' replaces core validation entirely, allowing custom return types without executing Pydantic internal coercion.","ValidationInfo.data exposes only sibling fields defined before the current field in model definition order; downstream fields are absent."],"goal":"Validate and sanitize field values across field_validator modes before, after, wrap, and plain with ValidationInfo sibling field access in Pydantic 2.13.4","kind":"HOW","packages":["pkg:pypi/pydantic@2.13.4"],"schemaVersion":1,"symbols":["pydantic.BaseModel","pydantic.ValidationError","pydantic.ValidationInfo","pydantic.field_validator"]},"contractCommand":["python","test/contract.py"],"environment":{"arch":"x64","ecosystem":"pypi","executionContext":"python","language":"python","os":"linux","packageManager":"pip","runtime":"python","schemaVersion":1},"license":"MIT-0","packages":["pkg:pypi/pydantic@2.13.4"],"schemaVersion":1,"subject":"pkg:pypi/pydantic@2.13.4","symbols":["pydantic.BaseModel","pydantic.ValidationError","pydantic.ValidationInfo","pydantic.field_validator"],"verifierAdapter":"python@1"}
annotated-types==0.8.0
pydantic==2.13.4
pydantic-core==2.46.4
typing-extensions==4.15.0
typing-inspection==0.4.2
annotated-types==0.8.0
pydantic==2.13.4
pydantic-core==2.46.4
typing-extensions==4.15.0
typing-inspection==0.4.2
{
"schemaVersion": 1,
"goal": "Validate and sanitize field values across field_validator modes before, after, wrap, and plain with ValidationInfo sibling field access in Pydantic 2.13.4",
"kind": "HOW",
"packages": [
"pkg:pypi/pydantic@2.13.4"
],
"symbols": [
"pydantic.BaseModel",
"pydantic.ValidationError",
"pydantic.ValidationInfo",
"pydantic.field_validator"
]
}
"""Pydantic field_validator demonstration package."""
"""Pydantic field_validator modes and ValidationInfo demonstration models."""
from typing import Any
from pydantic import BaseModel, Field, ValidationInfo, ValidatorFunctionWrapHandler, field_validator
class DefaultAfterValidatorModel(BaseModel):
# mode='after' is the default for field_validator
port: int
@field_validator("port")
@classmethod
def validate_port_range(cls, v: int) -> int:
if not (1 <= v <= 65535):
raise ValueError("Port must be between 1 and 65535")
return v
class BeforeValidatorModel(BaseModel):
# mode='before' receives raw input before core coercion
port: int
@field_validator("port", mode="before")
@classmethod
def sanitize_raw_port(cls, v: Any) -> Any:
if isinstance(v, str):
cleaned = v.strip()
if cleaned.startswith("port:"):
cleaned = cleaned[5:].strip()
return cleaned
return v
class WrapValidatorModel(BaseModel):
# mode='wrap' receives handler and raw value
score: int
@field_validator("score", mode="wrap")
@classmethod
def handle_score(
cls, v: Any, handler: ValidatorFunctionWrapHandler, info: ValidationInfo
) -> int:
if v == "N/A" or v is None:
return 0
try:
validated = handler(v)
return validated
except Exception:
# Fallback or custom recovery
return -1
class PlainValidatorModel(BaseModel):
# mode='plain' bypasses Pydantic's internal type coercion
raw_tag: int
@field_validator("raw_tag", mode="plain")
@classmethod
def custom_plain_validator(cls, v: Any) -> Any:
# Pydantic does not coerce v to int; whatever returned is accepted
if not isinstance(v, (int, str)):
raise ValueError("Must be int or str")
return f"TAG_{v}"
class FieldOrderValidationInfoModel(BaseModel):
first_field: str
second_field: str
third_field: str
@field_validator("second_field")
@classmethod
def check_second(cls, v: str, info: ValidationInfo) -> str:
# first_field is in info.data, third_field is NOT
assert "first_field" in info.data
assert "third_field" not in info.data
return v
@field_validator("third_field")
@classmethod
def check_third(cls, v: str, info: ValidationInfo) -> str:
# first_field and second_field are in info.data
assert "first_field" in info.data
assert "second_field" in info.data
return v
import sys
from importlib.metadata import version
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
from pydantic import ValidationError
from src.models import (
BeforeValidatorModel,
DefaultAfterValidatorModel,
FieldOrderValidationInfoModel,
PlainValidatorModel,
WrapValidatorModel,
)
def error_types(error: ValidationError) -> set[str]:
return {item["type"] for item in error.errors()}
def test_default_after_validator_runs_after_coercion() -> None:
# Valid coerced string '8080' succeeds and passes through after-validator
m = DefaultAfterValidatorModel(port="8080")
assert m.port == 8080
# Range check in after-validator fails with value_error
try:
DefaultAfterValidatorModel(port=70000)
except ValidationError as err:
assert "value_error" in error_types(err)
else:
raise AssertionError("Expected ValidationError for port > 65535")
# In mode='after', invalid string 'port:8080' fails at core int parsing BEFORE after-validator runs
try:
DefaultAfterValidatorModel(port="port:8080")
except ValidationError as err:
assert "int_parsing" in error_types(err)
else:
raise AssertionError("Expected int_parsing ValidationError for uncoerced string")
def test_mode_before_validator_intercepts_raw_input() -> None:
# mode='before' sanitizes ' port:8080 ' to '8080', which Pydantic then coerces to 8080
m = BeforeValidatorModel(port=" port:8080 ")
assert m.port == 8080
assert isinstance(m.port, int)
def test_mode_wrap_validator_controls_validation_and_recovery() -> None:
# 'N/A' is caught before handler and mapped to 0
m1 = WrapValidatorModel(score="N/A")
assert m1.score == 0
# Normal valid value '95' is passed to handler and coerced to 95
m2 = WrapValidatorModel(score="95")
assert m2.score == 95
# Invalid string 'invalid' fails in handler and wrap validator recovers with -1
m3 = WrapValidatorModel(score="invalid")
assert m3.score == -1
def test_mode_plain_validator_replaces_core_validation() -> None:
# mode='plain' bypasses standard int coercion and returns 'TAG_hello'
m = PlainValidatorModel(raw_tag="hello")
assert m.raw_tag == "TAG_hello"
assert isinstance(m.raw_tag, str)
m2 = PlainValidatorModel(raw_tag=123)
assert m2.raw_tag == "TAG_123"
def test_validation_info_data_reflects_field_definition_order() -> None:
# Validates that info.data only contains fields defined prior to the current field
m = FieldOrderValidationInfoModel(
first_field="alpha",
second_field="beta",
third_field="gamma",
)
assert m.first_field == "alpha"
assert m.second_field == "beta"
assert m.third_field == "gamma"
if __name__ == "__main__":
assert version("pydantic") == "2.13.4"
test_default_after_validator_runs_after_coercion()
test_mode_before_validator_intercepts_raw_input()
test_mode_wrap_validator_controls_validation_and_recovery()
test_mode_plain_validator_replaces_core_validation()
test_validation_info_data_reflects_field_definition_order()
print("CONTRACT PASS: Pydantic field_validator execution modes and ValidationInfo verified")
오리진 시더
익명