Skip to main content

odsbox-diff

Build PyPI version Python versions License: Apache 2.0

A CLI and Python library to compare two instance hierarchies on one or two ASAM ODS servers and write a structured diff result. Built on odsbox for ODS access and deepdiff for the comparison.

Typical use cases:

  • Verify that a test, test step or measurement was migrated faithfully between two servers.
  • Detect unintended changes between two snapshots of the same instance.
  • Hash bulk LocalColumn data to detect changes in measured signals.
  • Regression testing — compare a saved baseline file against a live server instance.
  • Create baselines — collect a hierarchy snapshot to a file for later comparison, with optional round-trip validation.
  • Offline comparison — diff two previously saved hierarchy files without any server connection.

Full user guide: see docs/usage.md for comprehensive CLI examples, Python API reference, pytest integration patterns, and troubleshooting.

Installation

uv add odsbox-diff
# or, with pip
pip install odsbox-diff

The package requires Python 3.14+ and ships a console script odsbox-diff.

Run uv run odsbox-diff --help to see the available commands (diff, collect, and config). Use uv run odsbox-diff COMMAND --help for command-specific options.

Quick start

  1. Create a starter config:
uv run odsbox-diff config

Default output is ./odsbox-diff.config.toml with three use-case server entries: default (basic), production (m2m), and staging (oidc).

Or copy one of the example configs from configs/ and adjust it:

  • config.example.toml — basic auth (single or multiple servers)
  • config.m2m.example.toml — OAuth2 machine-to-machine
  • config.oidc.example.toml — OIDC (interactive browser login)
  1. Store the secret (password / client_secret) in your OS keyring (see Keyring secrets below) or inline it in the config file.

  2. Run the diff:

    </code></pre>
    </li>
    </ol>
    <p>uv run odsbox-diff diff <code>
     --config my-config.toml </code>
    --entity TestStep <code>
     -id1 5 </code>
    -id2 7</p>
    <pre><code>
    With multiple named servers, prefix instance IDs with the server name:
    
    ```powershell
      uv run odsbox-diff diff `
        --config my-config.toml `
        --entity TestStep `
        -id1 prod:1898 `
        -id2 staging:2
    

    Compare two saved JSON files (no server connection needed):

      uv run odsbox-diff diff `
        --config my-config.toml `
        --entity TestStep `
        -id1 file:baseline.json `
        -id2 file:current.json
    
     The historical form without the explicit `diff` subcommand still works for
     backward compatibility.
    

    Collect a hierarchy to a file and self-validate:

    uv run odsbox-diff collect `
        --config my-config.toml `
        --entity TestStep `
        -id 42 `
        -o baseline.json `
        --validate
    

    CLI reference

    Flag Description
    -c, --config Path to a TOML or JSON config file (required).
    --entity Root entity name to compare (e.g. TestStep, Measurement).
    -id1 / -id2 Instance reference: plain ID (42), server:id (prod:5), or file:path.json to load from disk.
    -rf, --result_file Override the result-file path from config defaults.
    -ep, --exclude_path Extra DeepDiff path to exclude (repeatable).
    -erp, --exclude_regex_path Extra regex path exclusion (repeatable).
    -dd, --dump_dictionaries Also dump the collected hierarchies as <result>.inst1.json / .inst2.json.
    -bn, --no_bulk Skip bulk LocalColumn hashing.
    -bpb, --bulk_progress_bar Show a progress bar during bulk hashing.
    --cached-related ENTITY [...] Resolve relation IDs to names for the listed entities.
    -v, --verbose INFO logging with timestamps.
    -q, --quiet Suppress all logging.

    CLI flags always override config defaults. List options (exclude_path, exclude_regex_path, cached-related) extend the config defaults rather than replacing them.

    For backward compatibility, odsbox-diff --config ... --entity ... -id1 ... -id2 ... still runs the diff command implicitly.

    odsbox-diff collect (collect mode)

    Flag Description
    -c, --config Path to a TOML or JSON config file (required).
    --entity Root entity name to collect.
    -id Instance ID or server:id.
    -o, --output Output file path (.json or .zip).
    --validate After saving, reload and self-diff to verify round-trip fidelity.
    -rf, --result_file Path for the self-diff result (only with --validate).
    -bn, --no_bulk Skip bulk LocalColumn hashing.
    -bpb, --bulk_progress_bar Show a progress bar during bulk hashing.
    --cached-related ENTITY [...] Resolve relation IDs to names.
    -v, --verbose INFO logging with timestamps.
    -q, --quiet Suppress all logging.

    odsbox-diff config (config scaffolding)

    Flag Description
    -o, --output Output path (default: ./odsbox-diff.config.toml).
    --force Overwrite output file if it already exists.
    --single-auth {basic,m2m,oidc} Generate only one server section.
    --with-queries / --no-queries Include or skip queries.first and queries.second.
    --include-example-comments / --minimal Verbose guided output or compact output without comments.

    Exit codes

    Code Meaning
    0 No differences found (or collect completed successfully).
    100 Differences found; result file written.
    1 Argument or server:id validation error.
    -1 Uncaught exception.

    Keyring secrets

    Secrets are read from the OS keyring under the service name odsbox-diff:

    Auth method Keyring key
    basic <url>:<username> (password)
    m2m / oidc <token_endpoint>:<client_id> (client_secret)

    Example with the keyring CLI:

    keyring set odsbox-diff "http://localhost:57481/api:admin"
    

    Library usage

    from odsbox_diff import diff_file_to_file, diff_file_to_server, collect_to_file
    from odsbox_diff.connection import AppConfig, ServerConfig, AuthMethod
    
    # Compare two saved files — no server needed
    diff = diff_file_to_file("baseline.json", "current.json")
    assert not diff  # falsy = no differences
    
    # Regression test: baseline file vs live server
    diff = diff_file_to_server("my-config.toml", "TestStep", 42, "baseline.json")
    assert not diff
    
    # Build config in code (no config file needed)
    cfg = AppConfig(servers={"default": ServerConfig(
        url="http://localhost:8080/api",
        username="admin",
        password="secret",
    )})
    diff = diff_file_to_server(cfg, "TestStep", 42, "baseline.json")
    
    # Collect a baseline and validate round-trip fidelity
    result = collect_to_file("my-config.toml", "TestStep", 42, "baseline.json", validate=True)
    assert not result  # falsy = round-trip is clean
    

    Low-level building blocks

    from odsbox_diff import (
        collect,
        diff_dictionaries,
        dump_diff_as_json,
        save_collect_results,
    )
    from odsbox_diff.connection import create_connection, load_config
    
    app_config = load_config("my-config.toml")
    server = next(iter(app_config.servers.values()))
    
    with create_connection(server) as con_i:
        tree_a, _ = collect(con_i, "TestStep", 5)
        tree_b, _ = collect(con_i, "TestStep", 7)
    
    diff = diff_dictionaries(tree_a, tree_b, [], [])
    dump_diff_as_json("diff.json", diff)
    

    Configuration reference

    [server] / [servers.<name>]

    Either a single [server] table (stored internally as the default server) or one [servers.<name>] table per named server. Common fields:

    For single-server usage, [servers.default] is supported and behaves the same as [server].

    Field Type Notes
    url string ODS REST endpoint (required).
    verify_certificate bool Default true.
    method string basic, m2m, or oidc. Default basic.

    Method-specific fields are documented in the example configs in configs/.

    When using file-to-file comparisons only, the [server] section can be omitted entirely — only [defaults] is needed.

    [defaults]

    Optional defaults for diff behavior:

    Field Type Default
    result_file string "diff_ods_tests_result.json"
    exclude_paths list []
    exclude_regex_paths list [] (default Id / DateCreated / Version exclusions are always applied)
    cached_related list []
    bulk_progress_bar bool false
    no_bulk bool false
    dump_dictionaries bool false
    verbose bool false
    quiet bool false

    Development

    uv sync
    uv run pytest
    uv run ruff check src tests
    uv run mypy src
    

Metadata

Release files for odsbox-diff 1.2.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 odsbox-diff 1.2.0
File Size Uploaded
odsbox_diff-1.2.0.tar.gz 29.0 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for odsbox-diff 1.2.0
File Interpreter ABI Platform
odsbox_diff-1.2.0-py3-none-any.whl Python 3 none any Details

Total release size: 63.3 kB

Release files / odsbox_diff-1.2.0.tar.gz

Download URL odsbox_diff-1.2.0.tar.gz
Size 29.0 kB
Tags Source
SHA-256 checksum
How to use checksums
d7c568ed78304a743b4c2ab56eb2b5c8b5f1d40260d90b7f867b812e27815eff
BLAKE2b-256 checksum
How to use checksums
aec001c0e8a243460d0be13c7e69f8a6a961b5193b272eb450bedff0826aa47e
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.11.25 {"installer":{"name":"uv","version":"0.11.25","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release files / odsbox_diff-1.2.0-py3-none-any.whl

Download URL odsbox_diff-1.2.0-py3-none-any.whl
Size 34.4 kB
Tags Python 3
SHA-256 checksum
How to use checksums
f0e9af1fc050181f194b5228dc6e21b4ac54094aa693a9c9b80d88ae6d911c17
BLAKE2b-256 checksum
How to use checksums
4be6850a2cee86956d23d307560071c64be47eec6a33235723386ed7e695a809
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.11.25 {"installer":{"name":"uv","version":"0.11.25","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release history Release notifications | RSS feed

This release

1.2.0 This release

2 release files

1.1.0

2 release files

1.0.2

2 release files

1.0.1

2 release files

1.0.0

2 release files

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