daleq4py
A command line tool to establish the equivalence of two alternatively built Python wheels.
External dependency: Installing
daleq4pyfrom PyPI installs only the Python package. The separately installed Soufflé executable is required for inference-based commands; it is not bundled in thepy3-none-anywheel.
Requirements
- Python >= 3.12
- Poetry for dependency management and builds
- Soufflé available on
PATHfor inference-based commands:inference,compare-wheels, andrun-experiments. Soufflé is a separately installed system executable, not a Python package dependency.
Building
Install dependencies (including the dev group) and build the project:
poetry install
poetry build
poetry build produces the wheel and source distribution under dist/.
Running tests
poetry run pytest
Continuous integration
Every pull request runs the test workflow on GitHub Actions. It creates a clean Python environment from poetry.lock, installs the package and its development dependencies, then runs the complete pytest suite. Tests that require the separately installed Soufflé executable skip when it is not available on the runner.
Running
daleq4py
Compares two alternatively built wheels.
poetry run daleq4py WHEEL_A WHEEL_B
| Argument / option | Description |
|---|---|
WHEEL_A, WHEEL_B |
paths to the two wheels (.whl) to compare |
-v, --verbose |
enable verbose (debug) logging |
--version |
print the version and exit |
extract-edb
Extracts a content EDB (extensional database) from a single wheel into
<base-dir>/<wheel-name>/<session>/edb/:
content.tsv— every member of the wheel flattened into rows with columnsfile,path,key,value. Each member contributes:- Explicit keys when the member is a recognised metadata file inside
*.dist-info/: RFC 822Key: valuepairs forMETADATA/WHEEL, and one(packaged-file-path, sha256=…,size)row per line ofRECORD. - Synthetic keys (every member):
@sha256,@norm/sha256(text files only —.pyvia AST round-trip, other text via LF normalisation), and — when--srcis supplied —@src-exists,@src-exists-moved,@src/sha256,@src/norm/sha256.
- Explicit keys when the member is a recognised metadata file inside
package.tsv— a two-row package descriptor withname(the<package>-<version>token, e.g.colorlog-6.7.0) anddistinfo(the matching*.dist-infodirectory name).
The path of content.tsv is printed on success; package.tsv lives in the
same directory.
inference
Runs Soufflé on the EDB produced by extract-edb. The script concatenates
every rules/*.souffle file (each block prefixed with a // from <path>
provenance comment) into <session>/mergedEDBAndRules.souffle, substitutes
the four <*-facts> placeholders with absolute paths to the EDB (input) and
IDB (output) TSVs, creates the sibling <session>/idb/ directory, then
invokes souffle to produce idb/content.tsv and idb/package.tsv.
poetry run inference EDB_DIR
| Argument / option | Description |
|---|---|
EDB_DIR |
path to the edb/ directory produced by extract-edb |
-v, --verbose |
enable verbose (debug) logging |
Soufflé must be installed separately and its souffle binary must be on
PATH.
project-idb
Renders a human-readable markdown summary of an IDB directory. For every
*.tsv file in the input folder it emits a section whose header is the
predicate name (the filename stem) and whose body is a markdown table; the
column headers are inferred from the matching .decl declaration in any
rules/*.souffle file. The summary is written to
<session>/normalisation-summary.md.
poetry run project-idb IDB_DIR
| Argument / option | Description |
|---|---|
IDB_DIR |
path to the idb/ directory produced by inference |
--remove-derivation |
drop the leading id column from each table and write to normalisation-summary-reduced.md instead of normalisation-summary.md |
-v, --verbose |
enable verbose (debug) logging |
parse-provenance
Parses a derived-fact provenance id (an encoded proof tree such as
R1[F1,R2[F2,F3]]) and prints the resulting derivation tree. The grammar
lives in src/daleq4py/proof.lark and is the Lark equivalent of the
antlr4 Proof.g4 grammar in the daleq Java project.
poetry run parse-provenance 'R1[F1,R2[F2,F3,F4]]'
Output:
R1
├── F1
└── R2
├── F2
├── F3
└── F4
| Argument / option | Description |
|---|---|
EXPRESSION |
the derivation id to parse |
-v, --verbose |
enable verbose (debug) logging |
poetry run extract-edb WHEEL [--src ZIP]
| Argument / option | Description |
|---|---|
WHEEL |
path to the wheel (.whl) to extract content from |
--src ZIP |
optional "trusted" wheel/zip whose members define the baseline for the @src-* synthetic keys; when omitted, those keys are not emitted |
--base-dir DIR |
root directory for the EDB tree; a relative path is resolved against the project root (default: .souffle) |
-v, --verbose |
enable verbose (debug) logging |
The test suite writes its EDB files to .souffle-test/ instead.
normalise-pysrc
Normalises Python source files for binary-equivalence analysis by
round-tripping them through the stdlib AST (ast.parse + ast.unparse).
This:
- removes
#comments (the AST does not preserve them); - standardises layout to a canonical, deterministic form;
- writes
\nline endings unconditionally.
Docstrings are kept (they are string-literal expressions, not comments). Files that fail to parse are reported and skipped; the exit status is non-zero if any file failed.
poetry run normalise-pysrc PATH [PATH ...] (-o DIR | -i)
| Argument / option | Description |
|---|---|
PATH |
one or more Python source files or directories (directories are recursed for *.py) |
-o, --output DIR |
write normalised files under DIR, mirroring the input tree |
-i, --in-place |
overwrite source files in place |
-v, --verbose |
enable verbose (debug) logging |
Exactly one of --output or --in-place is required.
compare-wheels
Compares two wheels by partitioning their contents into three groups and applying a different equality test to each:
*.dist-infometadata — runsextract-edb,inferenceandproject-idb(twice, with and without--remove-derivation) on both wheels, then compares the reduced markdown summaries character by character.*.pysource files — normalises each shared.pymember with thenormalise-pysrcAST round-trip and compares the results character by character.- All other files — byte-by-byte comparison.
Members present in only one wheel count as a difference. The three per-group
booleans are combined with and to produce the overall result. Exit code:
0 if the wheels are equal, 1 if they differ, 2 on error.
poetry run compare-wheels WHEEL_A WHEEL_B [--src ZIP]
| Argument / option | Description |
|---|---|
WHEEL_A, WHEEL_B |
paths to the two wheels (.whl) to compare |
--src ZIP |
optional "trusted" wheel/zip whose .py members define the baseline set legitimately expected in a built wheel; any .py member of WHEEL_A or WHEEL_B not present in this baseline is flagged as suspicious. May be the same path as WHEEL_A, WHEEL_B, or any other zip. Does not affect the equality booleans — it's informational |
--base-dir DIR |
root directory for EDB / IDB / projection artefacts; a relative path is resolved against the project root (default: .souffle) |
-v, --verbose |
enable verbose (debug) logging |
The programmatic entry point
daleq4py.compare_wheels.compare_wheels(wheel_a, wheel_b) returns a
(bool, dict) pair. The dict contains, for each of the two wheels:
wheels— input wheel pathedb_dirs— extracted EDB directoryidb_dirs— inferred IDB directorysummaries,summaries_reduced— full and reduced markdown projectionsmerged_rules— path tomergedEDBAndRules.souffle
plus the per-group booleans metadata_equal, py_files_equal,
other_files_equal, and the lists of differing / one-sided member names for
the .py and other-file groups. When src is supplied, the dict also
carries src (the baseline path) and the two suspicious-member lists
py_files_unexpected_in_a / py_files_unexpected_in_b.
wheel-diff
Produces a markdown diff report between two wheels. The report lists, with
SHA-256 digests, the files present in only one wheel, the files that are
byte-identical in both, and the files that are present in both but differ —
each with a unified textual diff (for files that decode as text) or a
*Binary files differ.* note.
poetry run wheel-diff WHEEL_1 WHEEL_2 [-o FILE]
| Argument / option | Description |
|---|---|
WHEEL_1, WHEEL_2 |
paths to the two wheels (.whl) to diff |
-o, --output FILE |
write the report to FILE instead of stdout |
-v, --verbose |
enable verbose (debug) logging |
Adapted from pyeqpkg/diff-script/wheel-diff.py.
run-experiments
Sweeps sample-data/ and runs compare-wheels for every pair of builds of
the same (package, version). The expected sample layout is
sample-data/<package>/<version>/<build>/<wheel>.whl. One markdown report
per pair is written to:
sample-data-results/<package>/<version>/<build_a>-vs-<build_b>.md # comparison report
sample-data-results/<package>/<version>/<build_a>-vs-<build_b>-diff.md # wheel-diff report
sample-data-results/<package>/<version>/<build_a>-vs-<build_b>-idb-diff.md # unified diff of full IDB projections
sample-data-results/<package>/<version>/<build_a>-vs-<build_b>-idb-reduced-diff.md # unified diff of reduced IDB projections
The comparison report links to all three diff reports in its header. The two IDB-diff reports are only produced when the comparison pipeline ran successfully (they read the projection files it writes).
A top-level sample-data-results/summary.md is also written: it carries the
counts of equal / different / errored pairs and a details table (package,
build combination, overall / metadata / python source / other results) with
each row linking to its per-pair report.
Pipeline artefacts (EDB, IDB, projections, mergedEDBAndRules.souffle) for
each pair land under sample-data-results/.artefacts/<package>/<version>/<build_a>-vs-<build_b>/,
and the markdown reports link to them with relative paths. Each report lists:
- the overall result and the per-group results (metadata /
.py/ other); - a table of artefact paths for both builds;
- the names of any members that differ or appear in only one build.
For each pair the sweep also picks a "trusted" baseline (src) per the
following policy and passes it to compare-wheels: if macaron is one of
the pair's builds, the macaron wheel is src; otherwise the ossr wheel is
src. Each per-pair report ends with a Trust check section listing any
.py members in either build that are absent from the baseline. The trust
check is informational and does not affect the per-group / overall booleans.
A pair that raises during comparison is recorded as ERROR in its report;
the sweep continues with the next pair. Exit code: 0 if no pair errored,
1 otherwise.
poetry run run-experiments
| Argument / option | Description |
|---|---|
--sample-dir DIR |
sample-data root (default: sample-data) |
--results-dir DIR |
output root (default: sample-data-results) |
-v, --verbose |
enable verbose (debug) logging |
Use --help with any command for the full list of options.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file daleq4py-1.0.1.tar.gz.
File metadata
- Download URL: daleq4py-1.0.1.tar.gz
- Upload date:
- Size: 42.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
43eb6f7de1b1b77dbbc7029b3c57d2201b9346f98ca7e53484378699dfabf37d
|
|
| MD5 |
cf350df3862d9f902a1ffff7ef647e54
|
|
| BLAKE2b-256 |
299cc9b98090ed866cc4cd02529232c8a0f05def7a11c7fbef3fe7fc9de6a1e5
|
Provenance
The following attestation bundles were made for daleq4py-1.0.1.tar.gz:
Publisher:
pypi-publish.yaml on binaryeq/daleq4py
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
daleq4py-1.0.1.tar.gz -
Subject digest:
43eb6f7de1b1b77dbbc7029b3c57d2201b9346f98ca7e53484378699dfabf37d - Sigstore transparency entry: 2329372236
- Sigstore integration time:
-
Permalink:
binaryeq/daleq4py@3df48b992a02e3b7158704794883db8abe009d98 -
Branch / Tag:
refs/tags/v1.0.1 - Owner: https://github.com/binaryeq
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
pypi-publish.yaml@3df48b992a02e3b7158704794883db8abe009d98 -
Trigger Event:
push
-
Statement type:
File details
Details for the file daleq4py-1.0.1-py3-none-any.whl.
File metadata
- Download URL: daleq4py-1.0.1-py3-none-any.whl
- Upload date:
- Size: 48.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
9cd602ec3f94197b6a9f55441aecbf00e2252b57b70186d982cd1f3222eb6fdd
|
|
| MD5 |
11762ca9aa589b67ba904d2a93c25ea5
|
|
| BLAKE2b-256 |
6c0cdc18970ee3ffc33b54a352c51581508ef87da863141ee9b68fc7390d5454
|
Provenance
The following attestation bundles were made for daleq4py-1.0.1-py3-none-any.whl:
Publisher:
pypi-publish.yaml on binaryeq/daleq4py
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
daleq4py-1.0.1-py3-none-any.whl -
Subject digest:
9cd602ec3f94197b6a9f55441aecbf00e2252b57b70186d982cd1f3222eb6fdd - Sigstore transparency entry: 2329372539
- Sigstore integration time:
-
Permalink:
binaryeq/daleq4py@3df48b992a02e3b7158704794883db8abe009d98 -
Branch / Tag:
refs/tags/v1.0.1 - Owner: https://github.com/binaryeq
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
pypi-publish.yaml@3df48b992a02e3b7158704794883db8abe009d98 -
Trigger Event:
push
-
Statement type: