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 表示只有作者;大于 1 表示还有其他人构建过。密钥是自行生成的,背后没有注册身份,因此计的是密钥而非人。 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")

原始种子者

匿名