Skip to content

Fix _ensure_container dropping flags when target is already a config (#580)#1307

Open
jbbqqf wants to merge 1 commit into
omry:mainfrom
jbbqqf:fix/580-ensure-container-flags
Open

Fix _ensure_container dropping flags when target is already a config (#580)#1307
jbbqqf wants to merge 1 commit into
omry:mainfrom
jbbqqf:fix/580-ensure-container-flags

Conversation

@jbbqqf
Copy link
Copy Markdown

@jbbqqf jbbqqf commented May 21, 2026

Summary

omegaconf._utils._ensure_container silently dropped its flags argument when the input was already a DictConfig or ListConfig. The first two branches construct fresh containers via OmegaConf.create(..., flags=flags) / OmegaConf.structured(..., flags=flags), but the third branch (already-a-config) returned the target unchanged. This PR applies the flags in that path so the contract is uniform.

Fixes #580.

Context

Issue #580 (filed by @Jasha10) shows the divergence:

  • _ensure_container({...}, flags={"my_flag": True}) → flag set
  • _ensure_container(DataclassUser, flags={"my_flag": True}) → flag set
  • _ensure_container(DictConfig({...}), flags={"my_flag": True}) → flag silently dropped

The function is internal but its flags: Optional[Dict[str, bool]] parameter advertises a single post-condition; this fix makes the four input shapes behave consistently.

Changes

  • omegaconf/_utils.py — split the not OmegaConf.is_config(target) branch into elif OmegaConf.is_config(target): apply flags + else: raise ValueError. Inline comment points at the issue and explains why the explicit _set_flag loop is needed (the primitive/structured branches never run for already-built configs).
  • tests/test_utils.py — new parametrized regression test_ensure_container_applies_flags covering dict, list, DictConfig, ListConfig inputs.
  • news/580.bugfix — towncrier entry.

Reproduce BEFORE/AFTER yourself (copy-paste)

# --- one-time setup ---
git clone https://github.com/omry/omegaconf.git /tmp/repro-580 && cd /tmp/repro-580
python -m venv .venv && . .venv/bin/activate
pip install -e . pyyaml attrs pytest pytest-mock pytest-benchmark pytest-timeout setuptools antlr4-tools antlr4-python3-runtime
python setup.py antlr

# Grab the regression test from this PR so BEFORE and AFTER run the same command.
git fetch https://github.com/jbbqqf/omegaconf.git fix/580-ensure-container-flags
git checkout FETCH_HEAD -- tests/test_utils.py

# --- BEFORE (origin/main) ---
git checkout origin/main -- omegaconf/_utils.py
pytest tests/test_utils.py::test_ensure_container_applies_flags -v
# Expected: 2 failures on the `dictconfig` and `listconfig` parametrizations
#           ("AssertionError: assert None is True").

# --- AFTER (this PR) ---
git checkout FETCH_HEAD -- omegaconf/_utils.py
pytest tests/test_utils.py::test_ensure_container_applies_flags -v
# Expected: 4 passed.

What I ran locally

  • pytest tests/test_utils.py::test_ensure_container_applies_flags -v → 4 passed on the branch, 2 failed on origin/main (the dictconfig / listconfig parametrizations).
  • pytest tests/test_utils.py tests/test_create.py tests/test_basic_ops_dict.py tests/test_basic_ops_list.py -q → 1607 passed, 1 skipped on the branch.

Edge cases tested

# Scenario Input Expected Verified by
1 Primitive dict {"name": "bond", "age": 7} flag applied via OmegaConf.create(..., flags=...) (unchanged path) test_ensure_container_applies_flags[dict]
2 Primitive list [1, 2, 3] flag applied via OmegaConf.create(..., flags=...) (unchanged path) test_ensure_container_applies_flags[list]
3 Pre-built DictConfig DictConfig(content={"name": "bond", "age": 7}) flag applied via the new _set_flag loop test_ensure_container_applies_flags[dictconfig] (was failing on main)
4 Pre-built ListConfig ListConfig(content=[1, 2, 3]) flag applied via the new _set_flag loop test_ensure_container_applies_flags[listconfig] (was failing on main)
5 Invalid input "abc" unchanged: still raises ValueError existing test_ensure_container_raises_ValueError still passes

Risk / blast radius

Narrow. All current internal callers of _ensure_container (omegaconf/omegaconf.py lines 430, 472, 1112, 1165) invoke it without flags=, so the new branch is only entered when an external user (or future caller) explicitly passes flags with a pre-built config — exactly the scenario that was silently broken. Mutation is in-place, matching how _set_flag works elsewhere; no new copy semantics.

Release note

Fix `omegaconf._utils._ensure_container` silently dropping the `flags` argument when called with a pre-built `DictConfig` or `ListConfig`.

PR drafted with assistance from Claude Code. The change was reviewed manually against omry/omegaconf's source and the issue body. The reproducer block above was used during development; it is the same one a reviewer can paste verbatim.

…mry#580)

When `_ensure_container` was invoked with a pre-built `DictConfig` or
`ListConfig`, both the primitive-container and structured-config
branches were skipped and the function returned the input unchanged —
silently discarding any `flags` argument the caller had passed. This
contradicted the signature, which advertises `flags` as a general
post-condition.

Apply the flags explicitly via `_set_flag` in that path. Adds a
parametrized regression test covering dict / list / DictConfig /
ListConfig inputs so the contract is exercised uniformly.
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.

_ensure_container does not set flags if target is already a DictConfig/ListConfig

1 participant