Skip to main content

Diffora

Tests Python License: MIT GitHub stars GitHub issues

Stop guessing what changed between configs. Diffora compares parsed configuration values and pinpoints added, removed, and changed fields—even across different formats. Pass two directories to compare their file inventories and the semantic contents of supported configuration files.

JSON · YAML · TOML · .env · cross-format · directory comparison · secret-aware · CI-friendly

Why Diffora?

  • Custom redaction: add --redact PATH rules for sensitive fields that don't use conventional secret-like names.

If this saves you time, star the repository and share it with another developer. Stars help other people discover useful open-source tools.

  • Useful paths, not noisy text diffs: services[0].port or services[id="api"].port.
  • Added and removed objects are expanded: see the individual leaf values instead of one giant object blob.
  • Match lists by identity: reordering services won't create fake changes when you use --list-key id.
  • Ignore known differences: repeat --ignore or use glob patterns.
  • Safer output by default: values under sensitive-looking keys are masked.
  • Automation-ready: JSON output and predictable exit codes.
  • Safe by default: comparisons never modify input files; the separate apply command only writes when explicitly requested.

Install

From a local clone:

python -m pip install .

Or install the latest code directly from GitHub:

python -m pip install "git+https://github.com/hashpalace/ConfigDiff.git"

Install from PyPI with python -m pip install diffora. Requires Python 3.9+. YAML parsing uses PyYAML. Python 3.9 and 3.10 use tomli for TOML; Python 3.11+ uses the standard library. The configdiff command remains available as a compatibility alias.

Quick start

diffora old.json new.json
diffora config.yaml config.toml
diffora .env.production .env.staging
diffora ./config/staging ./config/production

Example output:

+ database.pool_size = 20
- legacy_endpoint = "https://old.example"
~ database.port: 5432 -> 5433

Try the included demo files from the repository root:

diffora examples/before.json examples/after.json --summary

Formats are detected from file extensions. For files with custom names, specify each format:

diffora before.conf after.conf --left-format json --right-format yaml

Supported format names: json, yaml, toml, env, and dotenv.

Compare lists by a stable key

By default, lists are compared by index. If entries are objects with a stable identifier, match them by that field to avoid noisy diffs when entries are reordered. --list-key also accepts comma-separated nested fields, such as apiVersion,kind,metadata.name for Kubernetes resources. Alternatively, --ignore-order compares lists as multisets: order is ignored but duplicate counts still matter; unmatched changed objects are represented as removals and additions:

diffora old.json new.json --list-key id
diffora old.json new.json --ignore-order

For example, changes are reported as services[id="api"].port instead of depending on the item's array position. The option applies to lists of objects that contain a unique, non-null value for the chosen key. If either list doesn't meet that requirement, Diffora safely falls back to index comparison. For Kubernetes resource lists, use --list-key kubernetes: resources are matched by apiVersion, kind, metadata.namespace, and metadata.name; a missing namespace is treated as the empty namespace for cluster-scoped resources.

Ignore paths

Pass --ignore multiple times to skip fields that are expected to differ:

diffora old.yaml new.yaml --ignore metadata.generated_at --ignore 'services[*].replicas'

Patterns support * (any sequence of characters) and ? (one character). An exact path also ignores its descendants. For example, ignoring database ignores all of database.host, database.port, and nested paths below it.

JSON output and exit codes

Use machine-readable output in scripts. JSON output is a versioned object with schema_version and differences:

diffora old.json new.json --format json
diffora old.json new.json --format json --summary

Each difference includes path, kind, old, new, old_missing, and new_missing. The missing flags distinguish an absent value from an explicit JSON null. kind is added, removed, or changed. The output contract is documented in schemas/differences.schema.json; consumers should check schema_version before relying on the structure.

Exit code Meaning
0 No differences found
1 Differences found
2 File, input, or parsing error

For a concise text summary:

diffora old.yaml new.yaml --summary

Disable terminal colors with --no-color. Colors are otherwise enabled only when writing to an interactive terminal. Set the NO_COLOR environment variable to disable colors without changing your command.

Stdin, quiet mode, and output formats

Read one side from a pipe by using - and explicitly setting that side's format. A single stdin stream can supply only one side per invocation.

cat new.json | diffora old.json - --right-format json
git show HEAD:config.yaml | diffora - config.yaml --left-format yaml

For CI checks that only need a pass/fail result, --quiet suppresses standard output while preserving exit codes: 0 means identical, 1 means differences, and 2 means an input or parsing error.

diffora production.yaml staging.yaml --quiet

Choose --format markdown for a readable table in pull-request comments, --format unified for a unified diff of normalized paths and values, --format github-actions to emit escaped workflow warning annotations, or --format json-patch to generate an RFC 6902 patch:

diffora before.json after.json --format markdown
diffora before.json after.json --format unified
diffora before.json after.json --format github-actions
diffora before.json after.json --format json-patch --show-secrets > patch.json
diffora apply patch.json before.json

A unified output is a semantic diff of parsed paths and values, not a byte-for-byte diff of the source files.

.env parsing notes

Diffora handles common dotenv lines: KEY=value, optional export KEY=value, surrounding whitespace, blank values, quoted values, and comments. For quoted values, text after the closing quote must be whitespace or a comment. Values are compared literally: Diffora does not expand variables, execute shell syntax, or interpret escape sequences. Duplicate keys use the last value. This is a practical parser for comparison, not a full shell or dotenv runtime.

Secret handling

Built-in key-name masking is best-effort. Add project-specific paths or glob patterns with repeatable --redact; for example:

diffora before.yaml after.yaml --redact database.url --redact 'services[*].endpoint'

Exact custom paths also mask their descendants. Glob patterns use * and ?. These rules are added to the built-in sensitive-key detection. --show-secrets disables both built-in and custom masking, so use it deliberately.

Values whose path names look sensitive (such as password, token, secret, api_key, or private_key) are masked by default in both text and JSON output.

diffora old.env new.env
diffora old.env new.env --show-secrets

Only use --show-secrets when you are certain the output won't be exposed. Redaction is a best-effort key-name safeguard, not a complete secret scanner.

Input safety limits

Diffora reads at most 16 MiB from each input, including standard input. YAML aliases that create cyclic references are rejected; YAML structures are limited to 500,000 nodes and 256 levels of nesting. Direct calls to the Python compare_values API receive the same cycle, node-count, and nesting checks. These safeguards help avoid runaway recursion and excessive resource use when comparing untrusted configuration files. Parser diagnostics report the format and location when available without echoing source values.

Custom redaction

Add project-specific paths or glob patterns when a sensitive field does not have an obvious secret-like name:

diffora before.yaml after.yaml --redact database.url --redact 'services[*].endpoint'

Exact custom paths also mask descendants. Glob patterns support * and ?. These rules are added to built-in sensitive-key detection and apply to text, JSON, Markdown, and unified output. --show-secrets disables both built-in and custom masking; use it deliberately.

CLI reference

diffora LEFT RIGHT [options]

Options:
  --left-format FORMAT   Format of the original file
  --right-format FORMAT  Format of the comparison file
  --format FORMAT        Output: text, json, markdown, unified, github-actions, or json-patch
  --show-secrets         Display values under sensitive-looking keys
  --redact PATH          Redact an additional path or glob pattern; repeatable
  --no-color             Disable ANSI colors
  --list-key KEY         Match lists by stable key; use kubernetes for Kubernetes manifests
  --ignore-order         Ignore list order while preserving duplicate counts
  --ignore PATH          Ignore a path or glob pattern; repeatable
  --summary              Print counts of added, removed, and changed values
  -h, --help             Show help

JSON Patch output may contain secret values needed to reproduce the target exactly, so --format json-patch requires the explicit --show-secrets opt-in. Review the patch before sharing it. diffora apply PATCH.json TARGET.json applies add/remove/replace operations to a JSON target file; the target is only rewritten after the patch validates and applies successfully. Small lists use bounded LCS matching to generate minimal add/remove operations; very large list comparisons fall back to replacing the array to keep patch generation predictable.

Shell and CI examples

Fail a shell script when configuration values differ:

set -eu
diffora config.example.yaml config.production.yaml --quiet

GitHub Actions step that fails only when differences exist (parse errors also fail):

- name: Check configuration drift
  run: diffora config/staging.yaml config/production.yaml --quiet

If you want to show the differences in a CI log, omit --quiet; use --format json when a later step consumes the structured output.

Development

git clone https://github.com/hashpalace/ConfigDiff.git
cd ConfigDiff
python -m pip install -e .
python -m unittest discover -s tests -v

GitHub Actions tests Python 3.9–3.13.

Changelog

See CHANGELOG.md for version history.

Real-world examples

The examples/ directory includes a small service configuration comparison. Run diffora examples/services-before.yaml examples/services-after.yaml --list-key id --summary to see it. The benchmarks/ directory contains paired 100-service JSON fixtures for repeatable performance checks; run diffora benchmarks/before.json benchmarks/after.json --list-key id --summary. To measure Diffora's core latency and enforce the 5 ms median budget, use python -m benchmarks.bench_compare --runs 50 --budget-ms 5. To compare the same synthetic changes against DeepDiff, jd, and diffyml, install those tools and run python -m benchmarks.bench_competitors --runs 30 --require-all. The cross-tool report separates in-process library timings from end-to-end CLI timings (including process startup and parsing); diffyml receives an equivalent YAML serialization of the same dataset. All values are synthetic and illustrative, not production configuration.

Roadmap

Milestones and contribution ideas live in ROADMAP.md.

Contributing

Bug reports, ideas, and pull requests are welcome. Please include a focused test for behavior changes and read CONTRIBUTING.md before submitting a PR.

License

MIT — see LICENSE.

Metadata

Release files for diffora 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for diffora 0.1.0
File Size Uploaded Provenance
diffora-0.1.0.tar.gz 29.7 kB With provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for diffora 0.1.0
File Interpreter ABI Platform
diffora-0.1.0-py3-none-any.whl Python 3 none any Details

Total release size: 49.0 kB

Release files / diffora-0.1.0.tar.gz

Download URL diffora-0.1.0.tar.gz
Size 29.7 kB
Tags Source
SHA-256 checksum
How to use checksums
1445fc6e7cb7acdde336a97be22710d175c410dd4c30b3411571fae3484ba243
BLAKE2b-256 checksum
How to use checksums
a38c8def13d2d5abd7de36d945f2eff34d5b65e957fe51f3f97af5891f70e6bc
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 23, 2026.

Transparency log

Release files / diffora-0.1.0-py3-none-any.whl

Download URL diffora-0.1.0-py3-none-any.whl
Size 19.2 kB
Tags Python 3
SHA-256 checksum
How to use checksums
113f82fc4abfe93641ef1a32286fe68f920b429b6101c754d39778a7b7b4eef9
BLAKE2b-256 checksum
How to use checksums
9e806c42b0fdf5f4158f29f271db586ce8e645edd9d9db407113d8270bdece80
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 23, 2026.

Transparency log

Security for diffora 0.1.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for diffora-0.1.0.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 23, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 release files Full provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page