Skip to content

Env vars override the YAML config file, as documented (#19) - #63

Merged
craigmcchesney merged 2 commits into
mainfrom
fix/19-env-overrides-yaml
Sep 24, 2026
Merged

craigmcchesney merged 2 commits into
mainfrom
fix/19-env-overrides-yaml

Conversation

@craigmcchesney

Copy link
Copy Markdown
Collaborator

Closes #19. Implements plan/tickets/19/plan.md (merged in #62), tasks 1–8.

The bug

MldpConfig.from_yaml() did cls(**flat_data). pydantic-settings ranks init kwargs above env vars, so any key present in the YAML file silently ignored its MLDP_* variable — levels 2 and 3 of the documented priority were inverted on every MldpClient() that found a config file (including the tracked repo-root mldp-config.yaml).

Changes

  • config/config.py — a private _YamlValuesSource reads the flattened YAML from a module-level ContextVar; settings_customise_sources ranks it last (init, env, dotenv, secrets, yaml). from_yaml() sets the var, calls cls(), and resets in finally. Error handling and the missing-file fallback are unchanged. (D1)
  • config/loader.py — if config_object is not None: return config_object. Same values as the old nine-field rebuild; the misleading "with environment variable overrides" comment and log go with it. (D3)
  • Tests — TestConfigPrecedence in test_config.py drives real files through load_config() and from_yaml() with ambient MLDP_* cleared: env > YAML, YAML > default, absent key → env/default, int/bool coercion, lower-case env names, MLDP_CONFIG_FILE + override, explicit object built inside the patched env plus assertIs, ContextVar reset after a failed and a successful load, and D2 (overridden invalid value loads). The mocked test_load_config_from_yaml is removed. One end-to-end MldpClient case in test_mldp_client.py. 8 of the new tests fail against main; the two reset tests pass there trivially (nothing to leak).
  • doc/cookbook/connecting.md — known-bug callout and ToC pointer removed; per-key and D4 (explicit object) sentences added; the auto-discovery paragraph rewritten.
  • CLAUDE.md — records the invariant: YAML never goes in as init kwargs.
  • doc/release-notes/NEXT.md — new section, written as a silent behavior change for Q3 at the cut.

Checks

pytest tests/unit/ (737 passed), ruff check ., ruff format --check ., mypy src/ (Success), cookbook snippet checker, release-notes checker — all clean locally.

🤖 Generated with Claude Code

https://claude.ai/code/session_01PsRVNfPSn4SG7UiU7m7hQq

from_yaml() passed the file's values as constructor kwargs, which
pydantic-settings ranks above environment variables, so any key present in
the YAML file silently ignored its MLDP_* variable.  The values now reach
pydantic through a ContextVar-backed settings source ranked below env and
above defaults (plan/tickets/19/plan.md, D1).

- load_config(config_object=) returns the object as-is (level 1), replacing
  a nine-field rebuild whose comment and log claimed env overrides applied.
- Real-file precedence tests with ambient MLDP_* cleared replace the test
  that mocked from_yaml away; one end-to-end MldpClient case.
- Cookbook callout removed; CLAUDE.md records the invariant; NEXT.md
  records the silent behavior change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PsRVNfPSn4SG7UiU7m7hQq
Copilot AI lite review requested due to automatic review settings September 24, 2026 22:48

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🔵 Needs a closer look

Existing YAML-focused tests must also isolate ambient MLDP_* environment variables.

Review effort: Lite
Findings: None

What changed in this PR

Fixes configuration precedence so MLDP_* environment variables override YAML values while preserving explicit configuration objects.

Changes:

  • Adds a ContextVar-backed YAML settings source.
  • Updates loader behavior and expands precedence tests.
  • Updates documentation and release notes.
File Summary
tests/​unit/​test_mldp_client.py Adds end-to-end environment override coverage.
tests/​unit/​test_config.py Adds comprehensive precedence and isolation tests.
src/​dp_python_lib/​config/​loader.py Preserves explicit configuration objects.
src/​dp_python_lib/​config/​config.py Implements corrected settings-source precedence.
doc/​release-notes/​NEXT.md Documents the behavior change.
doc/​cookbook/​connecting.md Documents corrected configuration precedence.
CLAUDE.md Records the configuration invariant.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

- env_ignore_empty=True: once env outranks the YAML file, an empty MLDP_*
  variable (a compose ${VAR} whose source is unset) would otherwise beat a
  working file with a blank host, or fail to parse as a port.
- Move mldp_env() to tests/unit/mldp_env.py and add isolate_mldp_env(),
  applied in setUp to TestMldpConfig, TestConfigLoader, and
  TestMldpClientConfigIntegration.  The fix exposed test_from_yaml_valid,
  which the file used to shield, so an exported MLDP_* now broke 9 tests
  (8 on main); all pass with MLDP_* exported.
- Cookbook, NEXT.md, CLAUDE.md, and a dated scope note in the plan.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PsRVNfPSn4SG7UiU7m7hQq
@craigmcchesney

Copy link
Copy Markdown
Collaborator Author

Re the Copilot overview ("Existing YAML-focused tests must also isolate ambient MLDP_* environment variables"): agreed, and fixed in dbc1f7b.

With MLDP_* exported, 8 existing tests already failed on main. This PR made it 9: test_from_yaml_valid used to be protected because the YAML file won, and now the env var wins. The plan had left test isolation out of scope. That was wrong once the fix itself exposed a test, and the plan now has a dated note saying so.

  • mldp_env() moved to tests/unit/mldp_env.py. It's joined by isolate_mldp_env(self), which is applied in setUp for TestMldpConfig, TestConfigLoader, and TestMldpClientConfigIntegration. The full unit suite (739 tests) now passes with MLDP_* variables exported as well as on a clean shell.
  • The same commit sets env_ignore_empty=True. Once env outranks the file, an empty MLDP_* (for example a compose ${VAR} whose source is unset) would otherwise have beaten a working file with a blank host, or failed to parse as a port. Two new tests cover it; both fail without the setting. Documented in the cookbook, NEXT.md, and CLAUDE.md.

🤖 Generated with Claude Code

https://claude.ai/code/session_01PsRVNfPSn4SG7UiU7m7hQq

@craigmcchesney
craigmcchesney merged commit e6acada into main Sep 24, 2026
6 checks passed
@craigmcchesney
craigmcchesney deleted the fix/19-env-overrides-yaml branch September 24, 2026 23:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Environment variables silently fail to override YAML config values

2 participants