From c52f3a0a6e5897407b0c5b4e7d4c93c684e29b25 Mon Sep 17 00:00:00 2001 From: Zachariah Farahany Date: Tue, 10 Nov 2026 18:06:58 -0500 Subject: [PATCH 1/2] feat(eval): add the pydantic-settings validate_by_name GitHub task (MIT-618) Pydantic 2.14 leaves model_config as declared (pydantic#13825), so pydantic-settings ignored env vars and secrets named after an aliased field under validate_by_alias=False. Verified in containers: fails at the base on 2.14.0, passes with the fix, passes at the base on 2.13.5. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01Qc1CQTb8r67J32HpkhS43y --- tools/eval/github_tasks.py | 74 +++++++++++++++++++++++++++++++++++++- 1 file changed, 73 insertions(+), 1 deletion(-) diff --git a/tools/eval/github_tasks.py b/tools/eval/github_tasks.py index f93fd66..39a5499 100644 --- a/tools/eval/github_tasks.py +++ b/tools/eval/github_tasks.py @@ -163,6 +163,70 @@ def tests(*ids: str) -> str: ) +# Pydantic 2.14.0 (2026-10-08) no longer writes the resolved `validate_by_name` back into a +# model's `model_config`, which now stays as declared: pydantic/pydantic#13825 ("Do not mutate +# `model_config` attribute"), merged 2026-09-25 (authored and committed 2026-09-25). +# Release notes: https://github.com/pydantic/pydantic/releases/tag/v2.14.0 +PYDANTIC_CONFIG_AS_DECLARED = Upstream( + library="pydantic", + version="2.14.0", + released="2026-10-08", + changelog="https://github.com/pydantic/pydantic/releases/tag/v2.14.0", + upstream_change="pydantic-2.14-model-config-not-resolved", +) + +# Issue: https://github.com/pydantic/pydantic-settings/issues/1021 (2026-10-09): with +# `validate_by_alias=False`, environment variables and secret files named after an aliased +# field are ignored and the default is used. +# Fix: https://github.com/pydantic/pydantic-settings/pull/1022 (squash-merged 2026-10-13): the +# sources resolve "fields can be populated by name" themselves, applying the `populate_by_name` +# fallback and the `validate_by_alias=False` implication. The base is the squash commit's +# parent. Only tests/test_settings.py is laid over: its new tests have no comments or +# docstrings. tests/test_utils.py is left out, because it imports the fix's new helper. +# The two tests below fail at the base on Pydantic 2.14.0 ('default' == 'honeycrisp') and pass +# there on 2.13.5 (checked); the PR's third new test, on init kwargs, passes at the base. The +# error doesn't name a fix; not claimed as hard-set (the obvious fix wasn't tried). +# Cause words name pydantic's change, not the symptom. Not "implies" or "validate_by_name": a +# fix that only treats the symptom says them ("validate_by_alias=False implies by name"). +PYDANTIC_SETTINGS_BY_NAME = GitHubTask( + name="gh-pydantic-settings-validate-by-name", + repo="https://github.com/pydantic/pydantic-settings", + base_commit="f0dd3824b3cdacd65a4bda730a9754a1cfdcda13", + fix_commit="3b405d42a4c55c9e88864bf80cddac44caad7da7", + issue_url="https://github.com/pydantic/pydantic-settings/issues/1021", + fix_url="https://github.com/pydantic/pydantic-settings/pull/1022", + test_paths=("tests/test_settings.py",), + python="3.12", + pins=("pydantic==2.14.0",), + setup=f"uv pip install --quiet {EXCLUDE_NEWER} -e . pytest==9.1.1", + test_command=tests( + "tests/test_settings.py::test_validate_by_alias_false_when_using_name", + "tests/test_settings.py::test_validate_by_alias_false_with_secrets_when_using_name", + ), + upstream=PYDANTIC_CONFIG_AS_DECLARED, + discovery=Discovery( + symptom_words=("validate_by_alias", "alias", "environment variable", "default"), + cause_words=( + "no longer writes", + "stopped writing", + "written back", + "writes back", + "write back", + "writing back", + "no longer mutates", + "mutate model_config", + "mutates model_config", + "model_config as declared", + "resolved validate_by_name", + "2.14", + ), + library="pydantic", + ), + trigger="version_specific", + license="MIT", +) + + # Polars 2.0.0 (2026-10-06; first release candidate 2026-09-02): a lazy query's collect now # runs on the streaming engine, which doesn't keep a join's row order unless the join passes # `maintain_order`. Upgrade guide, "The Lazy API defaults to the streaming engine": @@ -328,6 +392,14 @@ def scenario(task: GitHubTask) -> Scenario: ) -TASKS: list[GitHubTask] = [PYTEST_BDD, TEKORE, PYDANTIC_SETTINGS, MLODA, PIP_TOOLS, MARKLASSIAN] +TASKS: list[GitHubTask] = [ + PYTEST_BDD, + TEKORE, + PYDANTIC_SETTINGS, + PYDANTIC_SETTINGS_BY_NAME, + MLODA, + PIP_TOOLS, + MARKLASSIAN, +] GITHUB = {task.name: scenario(task) for task in TASKS} From b949b68a8118506f2bdc53338ca347140aeb895b Mon Sep 17 00:00:00 2001 From: Zachariah Farahany Date: Tue, 10 Nov 2026 18:17:24 -0500 Subject: [PATCH 2/2] fix(eval): give every validate_by_name cause word its object (MIT-618) Bare verbs such as "written back" and "no longer writes" match a fix that only treats the symptom, and "write back" matches "write backend". The phrases now name model_config or validate_by_name. Re-checked with no hits against both base outputs, the laid-over tests, the base source, the symptom words, the edited function names and symptom-only sentences. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01Qc1CQTb8r67J32HpkhS43y --- tools/eval/github_tasks.py | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/tools/eval/github_tasks.py b/tools/eval/github_tasks.py index 39a5499..4999858 100644 --- a/tools/eval/github_tasks.py +++ b/tools/eval/github_tasks.py @@ -186,8 +186,11 @@ def tests(*ids: str) -> str: # The two tests below fail at the base on Pydantic 2.14.0 ('default' == 'honeycrisp') and pass # there on 2.13.5 (checked); the PR's third new test, on init kwargs, passes at the base. The # error doesn't name a fix; not claimed as hard-set (the obvious fix wasn't tried). -# Cause words name pydantic's change, not the symptom. Not "implies" or "validate_by_name": a -# fix that only treats the symptom says them ("validate_by_alias=False implies by name"). +# Cause words name pydantic's change, not the symptom, and every phrase carries its object +# (`model_config` or `validate_by_name`): a bare verb ("written back", "no longer writes") +# matches a fix that only treats the symptom ("never written back to the field"), and "write +# back" matches "write backend". Not "implies" or "validate_by_name" alone: a symptom-only fix +# says them ("validate_by_alias=False implies by name"). PYDANTIC_SETTINGS_BY_NAME = GitHubTask( name="gh-pydantic-settings-validate-by-name", repo="https://github.com/pydantic/pydantic-settings", @@ -207,17 +210,21 @@ def tests(*ids: str) -> str: discovery=Discovery( symptom_words=("validate_by_alias", "alias", "environment variable", "default"), cause_words=( - "no longer writes", - "stopped writing", - "written back", - "writes back", - "write back", - "writing back", + "back into model_config", + "back to model_config", "no longer mutates", + "no longer mutates model_config", "mutate model_config", "mutates model_config", + "mutating model_config", "model_config as declared", - "resolved validate_by_name", + "model_config no longer", + "model_config unchanged", + "leaves model_config", + "no longer writes validate_by_name", + "no longer sets validate_by_name", + "stopped writing validate_by_name", + "stopped setting validate_by_name", "2.14", ), library="pydantic",