odsbox-diff
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.mdfor 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
- 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-machineconfig.oidc.example.toml— OIDC (interactive browser login)
-
Store the secret (password /
client_secret) in your OS keyring (see Keyring secrets below) or inline it in the config file. -
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
odsbox-diff diff(recommended diff mode)Flag Description -c,--configPath to a TOML or JSON config file (required). --entityRoot entity name to compare (e.g. TestStep,Measurement).-id1/-id2Instance reference: plain ID ( 42),server:id(prod:5), orfile:path.jsonto load from disk.-rf,--result_fileOverride the result-file path from config defaults. -ep,--exclude_pathExtra DeepDiff path to exclude (repeatable). -erp,--exclude_regex_pathExtra regex path exclusion (repeatable). -dd,--dump_dictionariesAlso dump the collected hierarchies as <result>.inst1.json/.inst2.json.-bn,--no_bulkSkip bulk LocalColumn hashing. -bpb,--bulk_progress_barShow a progress bar during bulk hashing. --cached-related ENTITY [...]Resolve relation IDs to names for the listed entities. -v,--verboseINFO logging with timestamps. -q,--quietSuppress 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,--configPath to a TOML or JSON config file (required). --entityRoot entity name to collect. -idInstance ID or server:id.-o,--outputOutput file path ( .jsonor.zip).--validateAfter saving, reload and self-diff to verify round-trip fidelity. -rf,--result_filePath for the self-diff result (only with --validate).-bn,--no_bulkSkip bulk LocalColumn hashing. -bpb,--bulk_progress_barShow a progress bar during bulk hashing. --cached-related ENTITY [...]Resolve relation IDs to names. -v,--verboseINFO logging with timestamps. -q,--quietSuppress all logging. odsbox-diff config(config scaffolding)Flag Description -o,--outputOutput path (default: ./odsbox-diff.config.toml).--forceOverwrite output file if it already exists. --single-auth {basic,m2m,oidc}Generate only one server section. --with-queries/--no-queriesInclude or skip queries.firstandqueries.second.--include-example-comments/--minimalVerbose guided output or compact output without comments. Exit codes
Code Meaning 0No differences found (or collect completed successfully). 100Differences found; result file written. 1Argument or server:idvalidation error.-1Uncaught 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
keyringCLI:keyring set odsbox-diff "http://localhost:57481/api:admin"
Library usage
High-level API (recommended for test frameworks)
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 thedefaultserver) 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 urlstring ODS REST endpoint (required). verify_certificatebool Default true.methodstring basic,m2m, oroidc. Defaultbasic.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_filestring "diff_ods_tests_result.json"exclude_pathslist []exclude_regex_pathslist [](defaultId/DateCreated/Versionexclusions are always applied)cached_relatedlist []bulk_progress_barbool falseno_bulkbool falsedump_dictionariesbool falseverbosebool falsequietbool falseDevelopment
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)
| File | Size | Uploaded | |
|---|---|---|---|
| odsbox_diff-1.2.0.tar.gz | 29.0 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| 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}
|