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.
uv add odsbox-diff
# or, with pip
pip install odsbox-diffThe package requires Python 3.14+ and ships a console script odsbox-diff.
-
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:
uv run odsbox-diff ` --config my-config.toml ` --entity TestStep ` -id1 5 ` -id2 7
With multiple named servers, prefix instance IDs with the server name:
uv run odsbox-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 ` --config my-config.toml ` --entity TestStep ` -id1 file:baseline.json ` -id2 file:current.json
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
| 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.
| 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. |
| 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. |
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"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 cleanfrom 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)Either a single [server] table (stored internally as the default server) or
one [servers.<name>] table per named server. Common fields:
| 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.
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 |
uv sync
uv run pytest
uv run ruff check src tests
uv run mypy src