Skip to main content

A small, schema-driven YAML object merger and validator

Project description

config-cascade-merge

A small, schema-driven YAML object merger and validator written in Python. It loads a base schema, validates and applies ordered overlay operations, and emits the complete configuration.

Installation

config-cascade-merge requires Python 3.10 or newer.

python -m pip install config-cascade-merge

You can also install the latest development version directly from GitHub:

python -m pip install git+https://github.com/149segolte/config-cascade-merge.git

Quick start

config-cascade-merge --base_config base.yaml --overlays_dir overlays/

The complete configuration object is written to standard output as YAML. To select specific overlays and control their order explicitly, pass their paths with --overlays:

config-cascade-merge --base_config base.yaml \
  --overlays overlays/common.yaml overlays/workstation.yaml

--overlays and --overlays_dir are mutually exclusive. Directory contents are processed in lexical filename order; an explicit list is processed in the order given.

The executable also supports short options:

config-cascade-merge -b base.yaml -o overlays/

The module form is equivalent:

python -m config_cascade_merge -b base.yaml -o overlays/

Library usage

Use load_merge_plan to load the same validated data without invoking the command-line interface:

from config_cascade_merge import ConfigError, load_merge_plan

try:
    plan = load_merge_plan("base.yaml", "overlays/")
except ConfigError as error:
    print(f"Invalid configuration: {error}")
else:
    if plan is not None:
        config = plan.create_object()
        print(config)

load_merge_plan returns a MergePlan containing the normalized schema and the ordered, validated operations. An empty base file returns None, matching the CLI behavior. The function does not configure logging or exit the calling process. Schema and overlay failures derive from ConfigError, with the more specific SchemaError and OverlayError types available when callers need to distinguish them. Calling plan.create_object() (or create_object(plan)) constructs a new object and applies every operation. Fixed object fields that have not been assigned are None; maps and lists start empty.

The second argument can alternatively be an ordered list of overlay paths:

plan = load_merge_plan(
    "base.yaml",
    ["overlays/common.yaml", "overlays/workstation.yaml"],
)

Base schema

The base file describes the allowed configuration shape rather than containing configuration values.

# base.yaml
type: object
keys:
    profile:
        type: object
        keys:
            name:
                type: string
            active:
                type: boolean
    packages:
        type: list
        id: name
        value:
            type: object
            keys:
                name:
                    type: string
                version:
                    type: integer
    labels:
        type: map
        value:
            type: string

Schema types

Type Purpose Main fields
string, integer, float, boolean, any Primitive value -
object Fixed-key mapping keys, optional merge, optional id
map Arbitrary-key mapping with uniform values value, optional merge, optional id
list Ordered values with a uniform item schema value, optional merge, optional id
union Value matching one of at least two schemas value (list of schemas)
tagged_union Object selected by a discriminator field keys, tag.name, tag.options

The merge policy is either append (default) or override. An id identifies values for identity-based merging.

Example tagged union:

type: tagged_union
keys:
    label:
        type: string
tag:
    name: kind
    options:
        file:
            path:
                type: string
        service:
            port:
                type: integer
        disabled: null

Overlays

Each overlay has a non-empty name and an ordered list of operations. Paths start with .; . addresses the schema root.

# overlays/10-workstation.yaml
name: workstation
operations:
    - action: set
      path: .profile
      data:
          name: Ada
          active: true

    - action: merge
      path: .packages
      data:
          - name: ruff
            version: 1

    - action: test
      path: .profile.name
      data: Ada
      on_fail: warn
      message: unexpected profile

    - action: remove
      path: .labels.legacy

    - action: clear
      path: .packages

Operations

Action Behavior Required fields
set Creates or replaces a value; data must fully match the target schema path, data
merge Validates a recursive merge into an object, map, or list path, data
remove Nulls a fixed object field or deletes a dynamic map entry path
test Checks equality before later execution path, data; optional on_fail, message
clear Removes all entries from a map or list path

test.on_fail accepts:

  • error — stop execution (default)
  • warn — report the optional message and continue
  • skip — keep prior operations from this overlay and skip the remainder
  • drop — discard all operations from this overlay

These failure behaviors are applied per overlay while creating the object. drop rolls back earlier changes from that overlay, while skip preserves earlier changes and skips its remaining operations.

Validation and errors

The validator rejects malformed YAML, invalid schemas, unknown paths or fields, incompatible values, and unsupported operations. Errors include source filenames and line numbers when available:

overlays/10-workstation.yaml:8: Data at '.packages[0].version' must be integer, got str

The CLI exits with status 1 for invalid paths, schemas, or overlays. An empty base file exits successfully without processing overlays.

Development

Clone the repository, install development dependencies, and run the test suite:

git clone https://github.com/149segolte/config-cascade-merge.git
cd config-cascade-merge
uv sync --dev
uv run pytest

Build both source and wheel distributions:

uv build

Project layout:

src/config_cascade_merge/api.py         high-level library API
src/config_cascade_merge/cli.py         CLI entry point
src/config_cascade_merge/engine.py      merge-plan execution
src/config_cascade_merge/schema.py      schema parsing and normalization
src/config_cascade_merge/overlay.py     overlay loading and validation
src/config_cascade_merge/yaml_loader.py YAML loading with source locations
tests/                                  pytest test suite

See CONTRIBUTING.md for the contribution and release process.

License

Licensed under the Mozilla Public License 2.0.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

config_cascade_merge-0.9.0.tar.gz (27.0 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

config_cascade_merge-0.9.0-py3-none-any.whl (26.6 kB view details)

Uploaded Python 3

File details

Details for the file config_cascade_merge-0.9.0.tar.gz.

File metadata

  • Download URL: config_cascade_merge-0.9.0.tar.gz
  • Upload date:
  • Size: 27.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.12

File hashes

Hashes for config_cascade_merge-0.9.0.tar.gz
Algorithm Hash digest
SHA256 ffe7b54f6977edee9ba3e814fcff6d38266a3ac32c7cbec9671d295a5fae7c62
MD5 34526559f2fbadd80614de2214b147c5
BLAKE2b-256 e0ce9dd6c032a4e2f86a0d8671fad850905a3a23d911a9abae35b8f5b2f177fc

See more details on using hashes here.

File details

Details for the file config_cascade_merge-0.9.0-py3-none-any.whl.

File metadata

File hashes

Hashes for config_cascade_merge-0.9.0-py3-none-any.whl
Algorithm Hash digest
SHA256 bd15695f1cbd04c6033112f05659793a1ff843fce3705d199c4f967b7ea2de11
MD5 ee67b2d0153f4c290234878b61024962
BLAKE2b-256 78fc7251ac8d96c8de49fc5fc9aeb7157320afd291908c02425dd2710f1cd3d2

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page