Skip to content

Support deleting inherited YAML keys with !delete - #234

Open
amuraru wants to merge 2 commits into
mainfrom
plan/158-remove-keys-on-merge
Open

amuraru wants to merge 2 commits into
mainfrom
plan/158-remove-keys-on-merge

Conversation

@amuraru

@amuraru amuraru commented Nov 2, 2026

Copy link
Copy Markdown
Contributor

Description

Closes #158.

Add an explicit !delete YAML tag for removing inherited dictionary keys during hierarchical merging:

modules_to_deploy:
  module2: !delete

The resulting dictionary omits module2; null retains its existing behavior. Deletion works at any dictionary depth, including removing entire subtrees. Missing keys are no-ops, and later files may recreate deleted keys without restoring their previous contents.

Directives are consumed per file before interpolation, through the Python API and both CLIs, including precomputed and parallel processing. Reject payloads and unsupported placements, including anywhere inside lists or sets.

Also correct two directly coupled precomputed-processing cases: allow valid empty results after deleting the final key, and use the actual include-capable loader while computing parents so !delete in included files works.

Type of Change

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Documentation update
  • Code refactoring
  • Performance improvement
  • Test improvement

Testing

  • Tests pass locally with my changes
  • I have added tests that prove my fix is effective or that my feature works
  • New and existing unit tests pass locally with my changes
  • I have tested the changes manually

Validation on macOS, Python 3.13, from the isolated feature worktree using the existing shared virtualenv. Pytest used:

export PYTHONPATH="$PWD" TMPDIR="$PWD/.issue-158-test-scratch" PYTHONDONTWRITEBYTECODE=1
Command Result
../../.venv/bin/python -m pytest tests/ -q --cov=himl --cov-report=term-missing --cov-fail-under=80 --basetemp="$TMPDIR/final-pytest" -o addopts= -p no:cacheprovider Passed: 295 tests, 94.11% coverage
PATH="../../.venv/bin:$PATH" make lint Passed: fatal flake8 and mypy clean; four existing advisory E501/F841 warnings
uvx bandit -r himl/ --severity-level medium Passed: no medium/high findings
../../.venv/bin/python -m build --outdir dist Passed: source distribution and wheel
../../.venv/bin/python -m twine check dist/* Passed: both artifacts
git diff --check Passed

Added 35 regression cases covering parsing/cache copies, includes, nested/root/missing keys, nulls and literals, invalid locations, recreation, custom strategies, interpolation, YAML/JSON serialization, empty output, sibling isolation, and all four sequential/parallel and full/precomputed combinations. Multiprocessing tests use real workers, not a mocked pool.

Manual smoke checks imported the final built wheel, asserted that its package was used rather than the checkout, and invoked himl.main:run and himl.config_merger:run against temporary hierarchy files. Both produced the expected deletion output; merger precomputed mode also passed.

A separate read-only correctness review found no high-confidence bugs.

Checklist

  • My code follows the style guidelines of this project
  • I have performed a self-review of my own code
  • I have commented my code, particularly in hard-to-understand areas
  • I have made corresponding changes to the documentation
  • My changes generate no new warnings
  • Any dependent changes have been merged and published in downstream modules

Additional Notes

No new runtime dependencies or CLI options; no downstream changes are required.

!delete uses standard YAML tag syntax but has Himl-specific semantics. Raw-input consumers need a constructor for this tag; generated YAML/JSON contains no deletion markers. It is not a permanent ban or a list-item deletion mechanism.

Loader constructors remain scoped to subclasses. The existing include loader receives the constructor directly to retain static-type compatibility; parent precomputation now selects the same include-capable loader as worker processing. Broader existing global-loader behavior is intentionally not redesigned.

The Python-version/OS CI matrix and Docker build were not run locally.

amuraru and others added 2 commits November 2, 2026 18:53
Consume explicit deletion directives during per-file merging while preserving nulls and configured strategies. Cover includes, interpolation, empty results, and precomputed/parallel hierarchy processing.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Suppress only Bandit's unrecognized factory-loader false positive, following the existing safe include-loader convention.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
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.

Q. How to remove keys on merge?

1 participant