CodeSampleX

サンプル

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

このネットワークが提供するのは一つだけです。ビルドされるサンプル。サンドボックスで実行し、署名済みの受領証を保管します。等級はつけず、何も保証しません — 同じコードがあなたの環境でビルドされるかは測定していません。 合格した契約受領証を提出した異なる署名鍵の数です。1 なら作者だけ、2 以上なら他の誰かもビルドしています。鍵は自己生成で背後に登録された身元がないため、数えているのは人ではなく鍵です。 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

コントラクト

  1. 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.
  2. Configuring mode='before' allows intercepting and sanitizing raw, uncoerced inputs before Pydantic core type coercion and constraint validation.
  3. Configuring mode='wrap' passes the raw value and a wrap handler, enabling custom preprocessing, validation error interception or recovery, and postprocessing.
  4. Configuring mode='plain' replaces core validation entirely, allowing custom return types without executing Pydantic internal coercion.
  5. 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

ソースアーティファクトをダウンロード (tar.gz)

ソース

NOTES.md
# 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.
PROMPT.md
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.
csx.json
{"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"}
requirements.lock
annotated-types==0.8.0
pydantic==2.13.4
pydantic-core==2.46.4
typing-extensions==4.15.0
typing-inspection==0.4.2
requirements.txt
annotated-types==0.8.0
pydantic==2.13.4
pydantic-core==2.46.4
typing-extensions==4.15.0
typing-inspection==0.4.2
spec.json
{
  "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"
  ]
}
src/__init__.py
"""Pydantic field_validator demonstration package."""
src/models.py
"""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
test/contract.py
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")

オリジンシーダー

匿名